From 2e3722fbddfffe7fd71fab4c1164385055ac6bfc Mon Sep 17 00:00:00 2001 From: Hlalanathi Mashimbye <148991994+HlalanathiMashimbye@users.noreply.github.com> Date: Sat, 20 Jun 2026 11:34:26 +0200 Subject: [PATCH 001/189] chore: import mission-control + mission-authoring onto the upstream fork (#1) This repo is now a real GitHub fork of coderlevelup/4tronix-rover-simulator, so our work layers on David's history instead of a copied-in island, and we can open PRs upstream and move CI across to the main project later. Brought over (none of which upstream had): - mission-authoring/ and mission-control/ (the two Next.js apps) - .github/workflows/ci.yml (build + test gate for both apps + yard) - scripts/dev-yard.js and root package.json (cross-platform dev scripts) - yard/rover/{rover_physics,test_rover_physics}.py and .env.example (canonical steering model + tests; standalone, no conflict) Upstream's yard is kept as-is: David's service.py/rover_server.py/drivers.py have advanced past our old snapshot (photo capture, interruptible exec, SSE), so those are NOT overwritten. sim_recorder/sandbox consolidation and the copy-to-clipboard close-the-loop will follow as focused PRs. .gitignore: re-anchored lib/ -> /lib/ (so mission-authoring/src/lib is not ignored) and added node_modules, *.mp4, root package-lock. Co-authored-by: Claude Opus 4.8 --- .github/workflows/ci.yml | 71 + .gitignore | 16 +- .../learner/profile/route.ts | 71 + .../api-routes-backup/learner/stats/route.ts | 33 + .../missions/[missionId]/route.ts | 58 + .../missions/[missionId]/status/route.ts | 111 + .../api-routes-backup/missions/route.ts | 41 + .../missions/submit/route.ts | 55 + .../simulator/execute/route.ts | 47 + mission-authoring/.gitignore | 49 + mission-authoring/.hintrc | 9 + mission-authoring/AGENTS.md | 5 + mission-authoring/CLAUDE.md | 1 + mission-authoring/GIT_WORKFLOW.md | 155 + mission-authoring/README.md | 83 + .../docs/CODE_VALIDATION_SYSTEM.md | 325 + .../docs/MISSION_HISTORY_UPDATE_SUMMARY.md | 358 + .../docs/OPERATOR_MISSION_UPDATE.md | 212 + .../docs/QUICK_START_MISSION_HISTORY.md | 193 + .../docs/TROUBLESHOOTING_MISSION_HISTORY.md | 253 + .../CLOUD_PLATFORM_REPO_CONTEXT.md | 838 + .../SIMULATOR_CLOUD_RUN_APPROACH.md | 510 + ...NONYMOUS_LEARNER_IMPLEMENTATION_SUMMARY.md | 362 + .../docs/features/ANONYMOUS_LEARNER_SYSTEM.md | 376 + .../features/LEARNER_ID_QUICK_REFERENCE.md | 241 + .../MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md | 349 + .../docs/features/USAGE_GUIDE_LEARNER_ID.md | 514 + .../features/USER_STORY_27_IMPLEMENTATION.md | 389 + .../features/USER_STORY_35_IMPLEMENTATION.md | 317 + .../features/VALIDATION_IMPLEMENTATION.md | 361 + .../docs/guides/QUICK_START_DEMO.md | 170 + .../docs/guides/README_VALIDATION.md | 118 + mission-authoring/eslint.config.mjs | 18 + mission-authoring/jest-results.json | 1 + mission-authoring/jest.config.ts | 34 + mission-authoring/jest.setup.ts | 11 + mission-authoring/next.config.ts | 28 + mission-authoring/package-lock.json | 13439 +++++++++++++++ mission-authoring/package.json | 46 + mission-authoring/postcss.config.mjs | 7 + mission-authoring/public/rover-hero.jpg | Bin 0 -> 152870 bytes mission-authoring/public/rover-icon.svg | 11 + mission-authoring/public/terrain.jpg | Bin 0 -> 154042 bytes .../completion-notification.test.ts | 3 + .../integration/missions.api.test.ts | 365 + .../__tests__/integration/queue-order.test.ts | 3 + .../integration/simulator-execution.test.ts | 3 + .../integration/unsafe-module-blocked.test.ts | 361 + .../integration/video-display.test.ts | 234 + .../integration/websocket-delivery.test.ts | 3 + .../unit/FirestoreMissionRepository.test.ts | 122 + .../src/__tests__/unit/Learner.test.ts | 202 + .../src/__tests__/unit/MissionService.test.ts | 252 + .../src/__tests__/unit/allowlist.test.ts | 364 + .../src/__tests__/unit/anonymous-auth.test.ts | 170 + .../unit/confirmation-response.test.ts | 192 + .../src/__tests__/unit/editor.test.tsx | 3 + .../__tests__/unit/execution-timeout.test.ts | 3 + .../src/__tests__/unit/firebase-admin.test.ts | 103 + .../src/__tests__/unit/getLearnerID.test.ts | 123 + .../src/__tests__/unit/heartbeat.test.ts | 3 + .../src/__tests__/unit/rover-movement.test.ts | 3 + .../unit/sandbox-disallowed-commands.test.ts | 3 + .../src/__tests__/unit/validation.test.ts | 201 + .../src/__tests__/unit/video-player.test.tsx | 321 + .../challenges/[challengeId]/hint/route.ts | 48 + .../src/app/api/challenges/route.ts | 11 + .../src/app/api/challenges/submit/route.ts | 50 + .../src/app/api/leaderboard/route.ts | 81 + .../src/app/api/missions/route.ts | 54 + .../src/app/api/simulate/video/route.ts | 39 + mission-authoring/src/app/challenges/page.tsx | 744 + mission-authoring/src/app/favicon.ico | Bin 0 -> 25931 bytes mission-authoring/src/app/globals.css | 336 + mission-authoring/src/app/history/page.tsx | 23 + mission-authoring/src/app/layout.tsx | 59 + .../src/app/leaderboard/page.tsx | 21 + mission-authoring/src/app/mission/page.tsx | 28 + .../[missionId]/MissionVideoClient.tsx | 202 + .../src/app/missions/[missionId]/page.tsx | 11 + mission-authoring/src/app/missions/page.tsx | 254 + mission-authoring/src/app/page.tsx | 263 + .../src/app/simulator/[missionId]/page.tsx | 22 + .../components/MissionCard/MissionCard.tsx | 103 + .../src/components/challenge/BadgeDisplay.tsx | 237 + .../components/challenge/ChallengeCard.tsx | 157 + .../challenge/ChallengeDashboard.tsx | 309 + .../challenge/ChallengeSubmissionResult.tsx | 264 + .../src/components/challenge/HintPanel.tsx | 163 + .../components/challenge/LeaderboardView.tsx | 192 + .../src/components/challenge/LevelDisplay.tsx | 163 + .../examples/SimpleLearnerExample.tsx | 212 + .../src/components/layout/Navbar.tsx | 257 + .../components/layout/NotificationModal.tsx | 142 + .../src/components/layout/PageTransition.tsx | 14 + .../src/components/learner/EmailPrompt.tsx | 83 + .../components/learner/LearnerDashboard.tsx | 175 + .../components/learner/LearnerProfileCard.tsx | 132 + .../src/components/mission/BlocklyEditor.tsx | 478 + .../src/components/mission/EditorPanel.tsx | 242 + .../mission/ManualControlPanel.module.css | 1 + .../components/mission/ManualControlPanel.tsx | 80 + .../mission/ManualControlRealtime.module.css | 1 + .../mission/ManualControlRealtime.tsx | 209 + .../mission/MissionHistoryScaffold.tsx | 124 + .../components/mission/MissionNameInput.tsx | 220 + .../mission/MissionStatusScaffold.tsx | 221 + .../components/mission/MissionWorkspace.tsx | 258 + .../components/mission/MonacoCodeEditor.tsx | 295 + .../mission/RoverEditorScaffold.tsx | 29 + .../mission/RoverSimulatorScaffold.tsx | 346 + .../components/mission/SimulationPanel.tsx | 17 + .../src/components/mission/VideoPlayer.tsx | 330 + .../src/components/shared/ScaffoldCard.tsx | 37 + .../simulator/SimulatorScaffold.tsx | 135 + .../simulator/SimulatorVisualization.tsx | 462 + .../src/components/simulator/index.ts | 2 + .../src/contexts/AuthContext.tsx | 105 + .../src/contexts/LearnerContext.tsx | 205 + .../src/core/application/ValidationEngine.ts | 242 + .../application/services/AllowlistService.ts | 108 + .../core/application/services/BadgeService.ts | 270 + .../application/services/ChallengeService.ts | 236 + .../services/ChallengeSubmissionService.ts | 276 + .../services/MissionNotificationService.ts | 10 + .../application/services/MissionService.ts | 105 + .../services/MissionStatusService.ts | 41 + .../src/core/domain/entities/Badge.ts | 2 + .../src/core/domain/entities/Challenge.ts | 54 + .../src/core/domain/entities/Learner.ts | 193 + .../src/core/domain/entities/Mission.ts | 79 + .../repositories/IChallengeRepository.ts | 124 + .../domain/repositories/ILearnerRepository.ts | 144 + .../domain/repositories/IMissionRepository.ts | 72 + mission-authoring/src/data/badges.ts | 112 + mission-authoring/src/data/challenges.ts | 203 + .../src/hooks/useChallengeSubmit.ts | 217 + mission-authoring/src/hooks/useLearnerID.ts | 50 + .../src/hooks/useMissionSubmit.ts | 94 + .../FirestoreChallengeRepository.ts | 334 + .../persistence/FirestoreLearnerRepository.ts | 461 + .../persistence/FirestoreMissionRepository.ts | 365 + .../persistence/firebase-admin.ts | 119 + .../sandbox/ast-allowlist-analyzer.ts | 202 + .../sandbox/rover-command-allowlist.ts | 151 + .../simulator/rover-movement.ts | 66 + .../simulator/simulator-executor.ts | 13 + .../validation/ValidationEngine.ts | 1 + .../src/infrastructure/validation/schemas.ts | 146 + mission-authoring/src/lib/anonymous-auth.ts | 141 + mission-authoring/src/lib/firebase.ts | 87 + mission-authoring/src/lib/getLearnerID.ts | 82 + .../src/lib/initializeLearner.ts | 163 + .../src/lib/missionNameGenerator.ts | 96 + mission-authoring/src/lib/rover-physics.ts | 187 + .../src/lib/services/learnerMissionService.ts | 249 + .../src/lib/services/missionQueryService.ts | 156 + mission-authoring/test-utils/nanoidMock.ts | 14 + mission-authoring/tsconfig.json | 36 + mission-control/.claude/settings.json | 17 + mission-control/.env.example | 21 + mission-control/.gitignore | 52 + mission-control/AGENTS.md | 5 + mission-control/CLAUDE.md | 1 + mission-control/README.md | 128 + mission-control/ROVER_CONFIG_ARCHITECTURE.md | 741 + mission-control/docs/API_CHANGES_SUMMARY.md | 327 + .../docs/CODE_VALIDATION_SYSTEM.md | 325 + .../docs/INTEGRATION_QUICK_START.md | 384 + mission-control/docs/INTEGRATION_SUMMARY.md | 376 + mission-control/docs/ROVER_API_V1.md | 414 + mission-control/docs/YARD_INTEGRATION_PLAN.md | 529 + .../docs/YARD_INTEGRATION_TECHNICAL_SPEC.md | 1109 ++ .../CLOUD_PLATFORM_REPO_CONTEXT.md | 838 + .../SIMULATOR_CLOUD_RUN_APPROACH.md | 510 + .../features/USER_STORY_27_IMPLEMENTATION.md | 389 + .../features/USER_STORY_35_IMPLEMENTATION.md | 317 + .../features/VALIDATION_IMPLEMENTATION.md | 361 + .../docs/guides/QUICK_START_DEMO.md | 170 + .../docs/guides/README_VALIDATION.md | 118 + mission-control/docs/openapi-rover-v1.yaml | 392 + .../docs/project-planning/TASK_TO_FILE_MAP.md | 194 + .../docs/project-planning/TEST_CASES.md | 682 + mission-control/eslint.config.mjs | 18 + mission-control/jest.config.ts | 32 + mission-control/jest.setup.ts | 11 + mission-control/migrate-claims.js | 206 + mission-control/next.config.ts | 30 + mission-control/package-lock.json | 13570 ++++++++++++++++ mission-control/package.json | 49 + mission-control/postcss.config.mjs | 7 + .../__tests__/e2e/non-operator-access.spec.ts | 2 + .../e2e/operator-executes-mission.spec.ts | 2 + .../rover/RoverHttpClient.test.ts | 424 + .../completion-notification.test.ts | 3 + .../__tests__/integration/queue-order.test.ts | 3 + .../integration/video-display.test.ts | 234 + .../integration/websocket-delivery.test.ts | 3 + .../unit/FirestoreMissionRepository.test.ts | 74 + .../src/__tests__/unit/MissionService.test.ts | 222 + .../unit/confirmation-response.test.ts | 192 + .../src/__tests__/unit/editor.test.tsx | 3 + .../src/__tests__/unit/emergency-stop.test.ts | 3 + .../__tests__/unit/execution-timeout.test.ts | 3 + .../src/__tests__/unit/firebase-admin.test.ts | 103 + .../src/__tests__/unit/heartbeat.test.ts | 3 + .../__tests__/unit/maintenance-mode.test.ts | 3 + .../src/__tests__/unit/queue-actions.test.ts | 3 + .../src/__tests__/unit/queue-ordering.test.ts | 3 + .../src/__tests__/unit/role-check.test.ts | 3 + .../src/__tests__/unit/rover-movement.test.ts | 3 + .../src/__tests__/unit/video-player.test.tsx | 321 + .../src/app/api/auth/session/route.ts | 75 + .../app/api/auth/set-custom-claims/route.ts | 107 + .../operator/missions/[missionId]/route.ts | 275 + .../src/app/api/operator/missions/route.ts | 48 + .../app/api/operator/queue/[yardId]/route.ts | 27 + .../rover-configs/[configId]/route.ts | 93 + .../operator/rover-configs/active/route.ts | 57 + .../app/api/operator/rover-configs/route.ts | 65 + .../operator/rover/emergency-stop/route.ts | 121 + .../operator/yard/[yardId]/status/route.ts | 101 + .../src/app/api/operator/yard/health/route.ts | 29 + .../app/api/rover/execution-complete/route.ts | 91 + .../src/app/api/simulator/execute/route.ts | 95 + mission-control/src/app/favicon.ico | Bin 0 -> 25931 bytes mission-control/src/app/globals.css | 312 + mission-control/src/app/layout.tsx | 38 + mission-control/src/app/login/page.tsx | 21 + .../app/operator/(console)/config/page.tsx | 24 + .../src/app/operator/(console)/layout.tsx | 14 + .../src/app/operator/(console)/page.tsx | 7 + .../rover/[missionId]/RoverControlClient.tsx | 699 + .../app/operator/rover/[missionId]/page.tsx | 12 + .../src/app/operator/rover/layout.tsx | 9 + .../simulator/[missionId]/SimulatorClient.tsx | 103 + .../operator/simulator/[missionId]/page.tsx | 12 + .../src/app/operator/simulator/layout.tsx | 9 + mission-control/src/app/page.tsx | 5 + .../components/MissionCard/MissionCard.tsx | 105 + .../layout/FloatingOperatorButton.tsx | 51 + .../src/components/layout/Navbar.tsx | 106 + .../operator/EmergencyStopButton.tsx | 24 + .../src/components/operator/MissionQueue.tsx | 385 + .../components/operator/OperatorConsole.tsx | 65 + .../components/operator/OperatorDashboard.tsx | 183 + .../src/components/operator/OperatorLogin.tsx | 184 + .../components/operator/OperatorSidebar.tsx | 115 + .../components/operator/ProtectedRoute.tsx | 50 + .../components/operator/RoverConfigModule.tsx | 204 + .../components/operator/RoverSidebarPanel.tsx | 108 + .../operator/SimulatorTrajectoryPanel.tsx | 316 + .../src/components/operator/YardControl.tsx | 123 + .../components/operator/YardServerHealth.tsx | 63 + .../components/operator/YardStatusCard.tsx | 155 + .../rover-config/ActiveRoverSelector.tsx | 529 + .../rover-config/RoverConfigCard.tsx | 358 + .../rover-config/RoverConfigForm.tsx | 478 + .../rover-config/RoverConfigList.tsx | 197 + .../src/components/ui/ScaffoldCard.tsx | 39 + .../src/components/ui/VideoPlayer.tsx | 330 + mission-control/src/contexts/AuthContext.tsx | 111 + .../core/application/dto/CreateMissionDto.ts | 10 + .../application/dto/CreateRoverConfigDto.ts | 13 + .../application/dto/RoverConfigResponseDto.ts | 13 + .../application/dto/UpdateRoverConfigDto.ts | 15 + .../services/GroundStationDispatchService.ts | 177 + .../application/services/MissionService.ts | 95 + .../services/MissionStatusService.ts | 41 + .../services/QueueActionService.ts | 12 + .../core/application/services/QueueService.ts | 10 + .../services/RoverConfigService.ts | 84 + .../application/services/YardStatusService.ts | 75 + .../src/core/domain/entities/Mission.ts | 79 + .../src/core/domain/entities/OperatorRole.ts | 6 + .../src/core/domain/entities/QueueAction.ts | 1 + .../src/core/domain/entities/RoverConfig.ts | 38 + .../src/core/domain/entities/Yard.ts | 9 + .../domain/repositories/IMissionRepository.ts | 61 + .../repositories/IRoverConfigRepository.ts | 12 + mission-control/src/hooks/useRoverConfig.ts | 187 + .../src/infrastructure/auth/firebase-admin.ts | 128 + .../infrastructure/auth/operator-claims.ts | 40 + .../auth/operator-role-check.ts | 14 + .../infrastructure/auth/set-custom-claims.ts | 73 + .../infrastructure/gsa/EmergencyStopSignal.ts | 6 + .../gsa/ExecutionTimeoutGuard.ts | 11 + .../infrastructure/gsa/GroundStationClient.ts | 13 + .../infrastructure/gsa/ReconnectionPolicy.ts | 7 + .../gsa/RoverHardwareAdapter.ts | 27 + .../gsa/SandboxedPythonExecutor.ts | 8 + .../MissionCompletionNotifier.ts | 9 + .../persistence/FirestoreMissionRepository.ts | 211 + .../FirestoreRoverConfigRepository.ts | 170 + .../persistence/firebase-admin.ts | 119 + .../infrastructure/rover/RoverHttpClient.ts | 249 + .../rover/types/RoverPayload.ts | 203 + .../validation/roverConfigValidation.ts | 45 + .../websocket/CloudRunMissionSocketServer.ts | 13 + mission-control/src/lib/firebase.ts | 82 + mission-control/src/lib/rover-type-utils.tsx | 79 + mission-control/src/proxy.ts | 213 + mission-control/tsconfig.json | 35 + package.json | 13 + scripts/dev-yard.js | 50 + yard/rover/.env.example | 20 + yard/rover/rover_physics.py | 164 + yard/rover/test_rover_physics.py | 86 + 308 files changed, 71355 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 mission-authoring/.archived/api-routes-backup/learner/profile/route.ts create mode 100644 mission-authoring/.archived/api-routes-backup/learner/stats/route.ts create mode 100644 mission-authoring/.archived/api-routes-backup/missions/[missionId]/route.ts create mode 100644 mission-authoring/.archived/api-routes-backup/missions/[missionId]/status/route.ts create mode 100644 mission-authoring/.archived/api-routes-backup/missions/route.ts create mode 100644 mission-authoring/.archived/api-routes-backup/missions/submit/route.ts create mode 100644 mission-authoring/.archived/api-routes-backup/simulator/execute/route.ts create mode 100644 mission-authoring/.gitignore create mode 100644 mission-authoring/.hintrc create mode 100644 mission-authoring/AGENTS.md create mode 100644 mission-authoring/CLAUDE.md create mode 100644 mission-authoring/GIT_WORKFLOW.md create mode 100644 mission-authoring/README.md create mode 100644 mission-authoring/docs/CODE_VALIDATION_SYSTEM.md create mode 100644 mission-authoring/docs/MISSION_HISTORY_UPDATE_SUMMARY.md create mode 100644 mission-authoring/docs/OPERATOR_MISSION_UPDATE.md create mode 100644 mission-authoring/docs/QUICK_START_MISSION_HISTORY.md create mode 100644 mission-authoring/docs/TROUBLESHOOTING_MISSION_HISTORY.md create mode 100644 mission-authoring/docs/architecture/CLOUD_PLATFORM_REPO_CONTEXT.md create mode 100644 mission-authoring/docs/architecture/SIMULATOR_CLOUD_RUN_APPROACH.md create mode 100644 mission-authoring/docs/features/ANONYMOUS_LEARNER_IMPLEMENTATION_SUMMARY.md create mode 100644 mission-authoring/docs/features/ANONYMOUS_LEARNER_SYSTEM.md create mode 100644 mission-authoring/docs/features/LEARNER_ID_QUICK_REFERENCE.md create mode 100644 mission-authoring/docs/features/MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md create mode 100644 mission-authoring/docs/features/USAGE_GUIDE_LEARNER_ID.md create mode 100644 mission-authoring/docs/features/USER_STORY_27_IMPLEMENTATION.md create mode 100644 mission-authoring/docs/features/USER_STORY_35_IMPLEMENTATION.md create mode 100644 mission-authoring/docs/features/VALIDATION_IMPLEMENTATION.md create mode 100644 mission-authoring/docs/guides/QUICK_START_DEMO.md create mode 100644 mission-authoring/docs/guides/README_VALIDATION.md create mode 100644 mission-authoring/eslint.config.mjs create mode 100644 mission-authoring/jest-results.json create mode 100644 mission-authoring/jest.config.ts create mode 100644 mission-authoring/jest.setup.ts create mode 100644 mission-authoring/next.config.ts create mode 100644 mission-authoring/package-lock.json create mode 100644 mission-authoring/package.json create mode 100644 mission-authoring/postcss.config.mjs create mode 100644 mission-authoring/public/rover-hero.jpg create mode 100644 mission-authoring/public/rover-icon.svg create mode 100644 mission-authoring/public/terrain.jpg create mode 100644 mission-authoring/src/__tests__/integration/completion-notification.test.ts create mode 100644 mission-authoring/src/__tests__/integration/missions.api.test.ts create mode 100644 mission-authoring/src/__tests__/integration/queue-order.test.ts create mode 100644 mission-authoring/src/__tests__/integration/simulator-execution.test.ts create mode 100644 mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts create mode 100644 mission-authoring/src/__tests__/integration/video-display.test.ts create mode 100644 mission-authoring/src/__tests__/integration/websocket-delivery.test.ts create mode 100644 mission-authoring/src/__tests__/unit/FirestoreMissionRepository.test.ts create mode 100644 mission-authoring/src/__tests__/unit/Learner.test.ts create mode 100644 mission-authoring/src/__tests__/unit/MissionService.test.ts create mode 100644 mission-authoring/src/__tests__/unit/allowlist.test.ts create mode 100644 mission-authoring/src/__tests__/unit/anonymous-auth.test.ts create mode 100644 mission-authoring/src/__tests__/unit/confirmation-response.test.ts create mode 100644 mission-authoring/src/__tests__/unit/editor.test.tsx create mode 100644 mission-authoring/src/__tests__/unit/execution-timeout.test.ts create mode 100644 mission-authoring/src/__tests__/unit/firebase-admin.test.ts create mode 100644 mission-authoring/src/__tests__/unit/getLearnerID.test.ts create mode 100644 mission-authoring/src/__tests__/unit/heartbeat.test.ts create mode 100644 mission-authoring/src/__tests__/unit/rover-movement.test.ts create mode 100644 mission-authoring/src/__tests__/unit/sandbox-disallowed-commands.test.ts create mode 100644 mission-authoring/src/__tests__/unit/validation.test.ts create mode 100644 mission-authoring/src/__tests__/unit/video-player.test.tsx create mode 100644 mission-authoring/src/app/api/challenges/[challengeId]/hint/route.ts create mode 100644 mission-authoring/src/app/api/challenges/route.ts create mode 100644 mission-authoring/src/app/api/challenges/submit/route.ts create mode 100644 mission-authoring/src/app/api/leaderboard/route.ts create mode 100644 mission-authoring/src/app/api/missions/route.ts create mode 100644 mission-authoring/src/app/api/simulate/video/route.ts create mode 100644 mission-authoring/src/app/challenges/page.tsx create mode 100644 mission-authoring/src/app/favicon.ico create mode 100644 mission-authoring/src/app/globals.css create mode 100644 mission-authoring/src/app/history/page.tsx create mode 100644 mission-authoring/src/app/layout.tsx create mode 100644 mission-authoring/src/app/leaderboard/page.tsx create mode 100644 mission-authoring/src/app/mission/page.tsx create mode 100644 mission-authoring/src/app/missions/[missionId]/MissionVideoClient.tsx create mode 100644 mission-authoring/src/app/missions/[missionId]/page.tsx create mode 100644 mission-authoring/src/app/missions/page.tsx create mode 100644 mission-authoring/src/app/page.tsx create mode 100644 mission-authoring/src/app/simulator/[missionId]/page.tsx create mode 100644 mission-authoring/src/components/MissionCard/MissionCard.tsx create mode 100644 mission-authoring/src/components/challenge/BadgeDisplay.tsx create mode 100644 mission-authoring/src/components/challenge/ChallengeCard.tsx create mode 100644 mission-authoring/src/components/challenge/ChallengeDashboard.tsx create mode 100644 mission-authoring/src/components/challenge/ChallengeSubmissionResult.tsx create mode 100644 mission-authoring/src/components/challenge/HintPanel.tsx create mode 100644 mission-authoring/src/components/challenge/LeaderboardView.tsx create mode 100644 mission-authoring/src/components/challenge/LevelDisplay.tsx create mode 100644 mission-authoring/src/components/examples/SimpleLearnerExample.tsx create mode 100644 mission-authoring/src/components/layout/Navbar.tsx create mode 100644 mission-authoring/src/components/layout/NotificationModal.tsx create mode 100644 mission-authoring/src/components/layout/PageTransition.tsx create mode 100644 mission-authoring/src/components/learner/EmailPrompt.tsx create mode 100644 mission-authoring/src/components/learner/LearnerDashboard.tsx create mode 100644 mission-authoring/src/components/learner/LearnerProfileCard.tsx create mode 100644 mission-authoring/src/components/mission/BlocklyEditor.tsx create mode 100644 mission-authoring/src/components/mission/EditorPanel.tsx create mode 100644 mission-authoring/src/components/mission/ManualControlPanel.module.css create mode 100644 mission-authoring/src/components/mission/ManualControlPanel.tsx create mode 100644 mission-authoring/src/components/mission/ManualControlRealtime.module.css create mode 100644 mission-authoring/src/components/mission/ManualControlRealtime.tsx create mode 100644 mission-authoring/src/components/mission/MissionHistoryScaffold.tsx create mode 100644 mission-authoring/src/components/mission/MissionNameInput.tsx create mode 100644 mission-authoring/src/components/mission/MissionStatusScaffold.tsx create mode 100644 mission-authoring/src/components/mission/MissionWorkspace.tsx create mode 100644 mission-authoring/src/components/mission/MonacoCodeEditor.tsx create mode 100644 mission-authoring/src/components/mission/RoverEditorScaffold.tsx create mode 100644 mission-authoring/src/components/mission/RoverSimulatorScaffold.tsx create mode 100644 mission-authoring/src/components/mission/SimulationPanel.tsx create mode 100644 mission-authoring/src/components/mission/VideoPlayer.tsx create mode 100644 mission-authoring/src/components/shared/ScaffoldCard.tsx create mode 100644 mission-authoring/src/components/simulator/SimulatorScaffold.tsx create mode 100644 mission-authoring/src/components/simulator/SimulatorVisualization.tsx create mode 100644 mission-authoring/src/components/simulator/index.ts create mode 100644 mission-authoring/src/contexts/AuthContext.tsx create mode 100644 mission-authoring/src/contexts/LearnerContext.tsx create mode 100644 mission-authoring/src/core/application/ValidationEngine.ts create mode 100644 mission-authoring/src/core/application/services/AllowlistService.ts create mode 100644 mission-authoring/src/core/application/services/BadgeService.ts create mode 100644 mission-authoring/src/core/application/services/ChallengeService.ts create mode 100644 mission-authoring/src/core/application/services/ChallengeSubmissionService.ts create mode 100644 mission-authoring/src/core/application/services/MissionNotificationService.ts create mode 100644 mission-authoring/src/core/application/services/MissionService.ts create mode 100644 mission-authoring/src/core/application/services/MissionStatusService.ts create mode 100644 mission-authoring/src/core/domain/entities/Badge.ts create mode 100644 mission-authoring/src/core/domain/entities/Challenge.ts create mode 100644 mission-authoring/src/core/domain/entities/Learner.ts create mode 100644 mission-authoring/src/core/domain/entities/Mission.ts create mode 100644 mission-authoring/src/core/domain/repositories/IChallengeRepository.ts create mode 100644 mission-authoring/src/core/domain/repositories/ILearnerRepository.ts create mode 100644 mission-authoring/src/core/domain/repositories/IMissionRepository.ts create mode 100644 mission-authoring/src/data/badges.ts create mode 100644 mission-authoring/src/data/challenges.ts create mode 100644 mission-authoring/src/hooks/useChallengeSubmit.ts create mode 100644 mission-authoring/src/hooks/useLearnerID.ts create mode 100644 mission-authoring/src/hooks/useMissionSubmit.ts create mode 100644 mission-authoring/src/infrastructure/persistence/FirestoreChallengeRepository.ts create mode 100644 mission-authoring/src/infrastructure/persistence/FirestoreLearnerRepository.ts create mode 100644 mission-authoring/src/infrastructure/persistence/FirestoreMissionRepository.ts create mode 100644 mission-authoring/src/infrastructure/persistence/firebase-admin.ts create mode 100644 mission-authoring/src/infrastructure/sandbox/ast-allowlist-analyzer.ts create mode 100644 mission-authoring/src/infrastructure/sandbox/rover-command-allowlist.ts create mode 100644 mission-authoring/src/infrastructure/simulator/rover-movement.ts create mode 100644 mission-authoring/src/infrastructure/simulator/simulator-executor.ts create mode 100644 mission-authoring/src/infrastructure/validation/ValidationEngine.ts create mode 100644 mission-authoring/src/infrastructure/validation/schemas.ts create mode 100644 mission-authoring/src/lib/anonymous-auth.ts create mode 100644 mission-authoring/src/lib/firebase.ts create mode 100644 mission-authoring/src/lib/getLearnerID.ts create mode 100644 mission-authoring/src/lib/initializeLearner.ts create mode 100644 mission-authoring/src/lib/missionNameGenerator.ts create mode 100644 mission-authoring/src/lib/rover-physics.ts create mode 100644 mission-authoring/src/lib/services/learnerMissionService.ts create mode 100644 mission-authoring/src/lib/services/missionQueryService.ts create mode 100644 mission-authoring/test-utils/nanoidMock.ts create mode 100644 mission-authoring/tsconfig.json create mode 100644 mission-control/.claude/settings.json create mode 100644 mission-control/.env.example create mode 100644 mission-control/.gitignore create mode 100644 mission-control/AGENTS.md create mode 100644 mission-control/CLAUDE.md create mode 100644 mission-control/README.md create mode 100644 mission-control/ROVER_CONFIG_ARCHITECTURE.md create mode 100644 mission-control/docs/API_CHANGES_SUMMARY.md create mode 100644 mission-control/docs/CODE_VALIDATION_SYSTEM.md create mode 100644 mission-control/docs/INTEGRATION_QUICK_START.md create mode 100644 mission-control/docs/INTEGRATION_SUMMARY.md create mode 100644 mission-control/docs/ROVER_API_V1.md create mode 100644 mission-control/docs/YARD_INTEGRATION_PLAN.md create mode 100644 mission-control/docs/YARD_INTEGRATION_TECHNICAL_SPEC.md create mode 100644 mission-control/docs/architecture/CLOUD_PLATFORM_REPO_CONTEXT.md create mode 100644 mission-control/docs/architecture/SIMULATOR_CLOUD_RUN_APPROACH.md create mode 100644 mission-control/docs/features/USER_STORY_27_IMPLEMENTATION.md create mode 100644 mission-control/docs/features/USER_STORY_35_IMPLEMENTATION.md create mode 100644 mission-control/docs/features/VALIDATION_IMPLEMENTATION.md create mode 100644 mission-control/docs/guides/QUICK_START_DEMO.md create mode 100644 mission-control/docs/guides/README_VALIDATION.md create mode 100644 mission-control/docs/openapi-rover-v1.yaml create mode 100644 mission-control/docs/project-planning/TASK_TO_FILE_MAP.md create mode 100644 mission-control/docs/project-planning/TEST_CASES.md create mode 100644 mission-control/eslint.config.mjs create mode 100644 mission-control/jest.config.ts create mode 100644 mission-control/jest.setup.ts create mode 100644 mission-control/migrate-claims.js create mode 100644 mission-control/next.config.ts create mode 100644 mission-control/package-lock.json create mode 100644 mission-control/package.json create mode 100644 mission-control/postcss.config.mjs create mode 100644 mission-control/src/__tests__/e2e/non-operator-access.spec.ts create mode 100644 mission-control/src/__tests__/e2e/operator-executes-mission.spec.ts create mode 100644 mission-control/src/__tests__/infrastructure/rover/RoverHttpClient.test.ts create mode 100644 mission-control/src/__tests__/integration/completion-notification.test.ts create mode 100644 mission-control/src/__tests__/integration/queue-order.test.ts create mode 100644 mission-control/src/__tests__/integration/video-display.test.ts create mode 100644 mission-control/src/__tests__/integration/websocket-delivery.test.ts create mode 100644 mission-control/src/__tests__/unit/FirestoreMissionRepository.test.ts create mode 100644 mission-control/src/__tests__/unit/MissionService.test.ts create mode 100644 mission-control/src/__tests__/unit/confirmation-response.test.ts create mode 100644 mission-control/src/__tests__/unit/editor.test.tsx create mode 100644 mission-control/src/__tests__/unit/emergency-stop.test.ts create mode 100644 mission-control/src/__tests__/unit/execution-timeout.test.ts create mode 100644 mission-control/src/__tests__/unit/firebase-admin.test.ts create mode 100644 mission-control/src/__tests__/unit/heartbeat.test.ts create mode 100644 mission-control/src/__tests__/unit/maintenance-mode.test.ts create mode 100644 mission-control/src/__tests__/unit/queue-actions.test.ts create mode 100644 mission-control/src/__tests__/unit/queue-ordering.test.ts create mode 100644 mission-control/src/__tests__/unit/role-check.test.ts create mode 100644 mission-control/src/__tests__/unit/rover-movement.test.ts create mode 100644 mission-control/src/__tests__/unit/video-player.test.tsx create mode 100644 mission-control/src/app/api/auth/session/route.ts create mode 100644 mission-control/src/app/api/auth/set-custom-claims/route.ts create mode 100644 mission-control/src/app/api/operator/missions/[missionId]/route.ts create mode 100644 mission-control/src/app/api/operator/missions/route.ts create mode 100644 mission-control/src/app/api/operator/queue/[yardId]/route.ts create mode 100644 mission-control/src/app/api/operator/rover-configs/[configId]/route.ts create mode 100644 mission-control/src/app/api/operator/rover-configs/active/route.ts create mode 100644 mission-control/src/app/api/operator/rover-configs/route.ts create mode 100644 mission-control/src/app/api/operator/rover/emergency-stop/route.ts create mode 100644 mission-control/src/app/api/operator/yard/[yardId]/status/route.ts create mode 100644 mission-control/src/app/api/operator/yard/health/route.ts create mode 100644 mission-control/src/app/api/rover/execution-complete/route.ts create mode 100644 mission-control/src/app/api/simulator/execute/route.ts create mode 100644 mission-control/src/app/favicon.ico create mode 100644 mission-control/src/app/globals.css create mode 100644 mission-control/src/app/layout.tsx create mode 100644 mission-control/src/app/login/page.tsx create mode 100644 mission-control/src/app/operator/(console)/config/page.tsx create mode 100644 mission-control/src/app/operator/(console)/layout.tsx create mode 100644 mission-control/src/app/operator/(console)/page.tsx create mode 100644 mission-control/src/app/operator/rover/[missionId]/RoverControlClient.tsx create mode 100644 mission-control/src/app/operator/rover/[missionId]/page.tsx create mode 100644 mission-control/src/app/operator/rover/layout.tsx create mode 100644 mission-control/src/app/operator/simulator/[missionId]/SimulatorClient.tsx create mode 100644 mission-control/src/app/operator/simulator/[missionId]/page.tsx create mode 100644 mission-control/src/app/operator/simulator/layout.tsx create mode 100644 mission-control/src/app/page.tsx create mode 100644 mission-control/src/components/MissionCard/MissionCard.tsx create mode 100644 mission-control/src/components/layout/FloatingOperatorButton.tsx create mode 100644 mission-control/src/components/layout/Navbar.tsx create mode 100644 mission-control/src/components/operator/EmergencyStopButton.tsx create mode 100644 mission-control/src/components/operator/MissionQueue.tsx create mode 100644 mission-control/src/components/operator/OperatorConsole.tsx create mode 100644 mission-control/src/components/operator/OperatorDashboard.tsx create mode 100644 mission-control/src/components/operator/OperatorLogin.tsx create mode 100644 mission-control/src/components/operator/OperatorSidebar.tsx create mode 100644 mission-control/src/components/operator/ProtectedRoute.tsx create mode 100644 mission-control/src/components/operator/RoverConfigModule.tsx create mode 100644 mission-control/src/components/operator/RoverSidebarPanel.tsx create mode 100644 mission-control/src/components/operator/SimulatorTrajectoryPanel.tsx create mode 100644 mission-control/src/components/operator/YardControl.tsx create mode 100644 mission-control/src/components/operator/YardServerHealth.tsx create mode 100644 mission-control/src/components/operator/YardStatusCard.tsx create mode 100644 mission-control/src/components/rover-config/ActiveRoverSelector.tsx create mode 100644 mission-control/src/components/rover-config/RoverConfigCard.tsx create mode 100644 mission-control/src/components/rover-config/RoverConfigForm.tsx create mode 100644 mission-control/src/components/rover-config/RoverConfigList.tsx create mode 100644 mission-control/src/components/ui/ScaffoldCard.tsx create mode 100644 mission-control/src/components/ui/VideoPlayer.tsx create mode 100644 mission-control/src/contexts/AuthContext.tsx create mode 100644 mission-control/src/core/application/dto/CreateMissionDto.ts create mode 100644 mission-control/src/core/application/dto/CreateRoverConfigDto.ts create mode 100644 mission-control/src/core/application/dto/RoverConfigResponseDto.ts create mode 100644 mission-control/src/core/application/dto/UpdateRoverConfigDto.ts create mode 100644 mission-control/src/core/application/services/GroundStationDispatchService.ts create mode 100644 mission-control/src/core/application/services/MissionService.ts create mode 100644 mission-control/src/core/application/services/MissionStatusService.ts create mode 100644 mission-control/src/core/application/services/QueueActionService.ts create mode 100644 mission-control/src/core/application/services/QueueService.ts create mode 100644 mission-control/src/core/application/services/RoverConfigService.ts create mode 100644 mission-control/src/core/application/services/YardStatusService.ts create mode 100644 mission-control/src/core/domain/entities/Mission.ts create mode 100644 mission-control/src/core/domain/entities/OperatorRole.ts create mode 100644 mission-control/src/core/domain/entities/QueueAction.ts create mode 100644 mission-control/src/core/domain/entities/RoverConfig.ts create mode 100644 mission-control/src/core/domain/entities/Yard.ts create mode 100644 mission-control/src/core/domain/repositories/IMissionRepository.ts create mode 100644 mission-control/src/core/domain/repositories/IRoverConfigRepository.ts create mode 100644 mission-control/src/hooks/useRoverConfig.ts create mode 100644 mission-control/src/infrastructure/auth/firebase-admin.ts create mode 100644 mission-control/src/infrastructure/auth/operator-claims.ts create mode 100644 mission-control/src/infrastructure/auth/operator-role-check.ts create mode 100644 mission-control/src/infrastructure/auth/set-custom-claims.ts create mode 100644 mission-control/src/infrastructure/gsa/EmergencyStopSignal.ts create mode 100644 mission-control/src/infrastructure/gsa/ExecutionTimeoutGuard.ts create mode 100644 mission-control/src/infrastructure/gsa/GroundStationClient.ts create mode 100644 mission-control/src/infrastructure/gsa/ReconnectionPolicy.ts create mode 100644 mission-control/src/infrastructure/gsa/RoverHardwareAdapter.ts create mode 100644 mission-control/src/infrastructure/gsa/SandboxedPythonExecutor.ts create mode 100644 mission-control/src/infrastructure/notifications/MissionCompletionNotifier.ts create mode 100644 mission-control/src/infrastructure/persistence/FirestoreMissionRepository.ts create mode 100644 mission-control/src/infrastructure/persistence/FirestoreRoverConfigRepository.ts create mode 100644 mission-control/src/infrastructure/persistence/firebase-admin.ts create mode 100644 mission-control/src/infrastructure/rover/RoverHttpClient.ts create mode 100644 mission-control/src/infrastructure/rover/types/RoverPayload.ts create mode 100644 mission-control/src/infrastructure/validation/roverConfigValidation.ts create mode 100644 mission-control/src/infrastructure/websocket/CloudRunMissionSocketServer.ts create mode 100644 mission-control/src/lib/firebase.ts create mode 100644 mission-control/src/lib/rover-type-utils.tsx create mode 100644 mission-control/src/proxy.ts create mode 100644 mission-control/tsconfig.json create mode 100644 package.json create mode 100644 scripts/dev-yard.js create mode 100644 yard/rover/.env.example create mode 100644 yard/rover/rover_physics.py create mode 100644 yard/rover/test_rover_physics.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d91d862 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: CI + +# Build + test gate. Set these jobs as REQUIRED status checks on main +# (Settings → Branches → main → Require status checks to pass) so a red +# build can never be merged again. + +on: + pull_request: + branches: [main] + push: + branches: [main] + +# Public Firebase web config is required at build time. These are placeholders +# so `next build` is deterministic in CI; they are NOT secrets (real values live +# in each app's .env, which is git-ignored). +env: + NEXT_PUBLIC_FIREBASE_API_KEY: ci-placeholder + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ci-placeholder.firebaseapp.com + NEXT_PUBLIC_FIREBASE_PROJECT_ID: ci-placeholder + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ci-placeholder.appspot.com + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: "0000000000" + NEXT_PUBLIC_FIREBASE_APP_ID: "1:0000000000:web:0000000000000000000000" + +jobs: + mission-authoring: + name: mission-authoring (build + test) + runs-on: ubuntu-latest + defaults: + run: + working-directory: mission-authoring + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: mission-authoring/package-lock.json + - run: npm ci + - run: npx next build + - run: npx jest --ci + + mission-control: + name: mission-control (build + test) + runs-on: ubuntu-latest + defaults: + run: + working-directory: mission-control + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: mission-control/package-lock.json + - run: npm ci + - run: npx next build + - run: npx jest --ci + + yard: + name: yard (python tests) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Install yard deps + run: pip install -r yard/rover/requirements.txt + - name: Run yard tests + working-directory: yard/rover + run: python -m pytest test_service.py test_rover_physics.py -q diff --git a/.gitignore b/.gitignore index 942fb16..67b1301 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,10 @@ dist/ downloads/ eggs/ .eggs/ -lib/ -lib64/ +# Anchored to repo root so these Python build dirs do NOT match the Next apps' +# source dirs (e.g. mission-authoring/src/lib/). +/lib/ +/lib64/ parts/ sdist/ var/ @@ -162,3 +164,13 @@ cython_debug/ # Yard satellite runtime config (rover URL edited via /status page) yard/satellite/satellite_config.json .DS_Store + +# Node (root workspace tooling + the two Next apps) +node_modules/ +npm-debug.log* + +# Generated simulation videos +*.mp4 + +# Root lockfile — dev-only tooling (concurrently); apps own their lockfiles +/package-lock.json diff --git a/mission-authoring/.archived/api-routes-backup/learner/profile/route.ts b/mission-authoring/.archived/api-routes-backup/learner/profile/route.ts new file mode 100644 index 0000000..fa3b4af --- /dev/null +++ b/mission-authoring/.archived/api-routes-backup/learner/profile/route.ts @@ -0,0 +1,71 @@ +/** + * Learner Profile API + * + * GET /api/learner/profile?sessionId=xxx - Get learner profile + * PATCH /api/learner/profile - Update learner profile + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { FirestoreLearnerRepository } from '@/infrastructure/persistence/FirestoreLearnerRepository'; + +const learnerRepo = new FirestoreLearnerRepository(); + +export async function GET(request: NextRequest) { + try { + const sessionId = request.nextUrl.searchParams.get('sessionId'); + + if (!sessionId) { + return NextResponse.json( + { error: 'Session ID required' }, + { status: 400 } + ); + } + + const learner = await learnerRepo.findBySessionId(sessionId); + + if (!learner) { + return NextResponse.json( + { error: 'Learner not found' }, + { status: 404 } + ); + } + + return NextResponse.json({ learner }); + } catch (error) { + console.error('Failed to get learner profile:', error); + return NextResponse.json( + { error: 'Failed to retrieve learner profile' }, + { status: 500 } + ); + } +} + +export async function PATCH(request: NextRequest) { + try { + const body = await request.json(); + const { sessionId, updates } = body; + + if (!sessionId) { + return NextResponse.json( + { error: 'Session ID required' }, + { status: 400 } + ); + } + + // Validate updates - only allow safe fields + const allowedUpdates: Partial = {}; + if (updates.displayName !== undefined) { + allowedUpdates.displayName = updates.displayName; + } + + await learnerRepo.update(sessionId, allowedUpdates); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Failed to update learner profile:', error); + return NextResponse.json( + { error: 'Failed to update learner profile' }, + { status: 500 } + ); + } +} diff --git a/mission-authoring/.archived/api-routes-backup/learner/stats/route.ts b/mission-authoring/.archived/api-routes-backup/learner/stats/route.ts new file mode 100644 index 0000000..3db269a --- /dev/null +++ b/mission-authoring/.archived/api-routes-backup/learner/stats/route.ts @@ -0,0 +1,33 @@ +/** + * Learner Statistics API + * + * GET /api/learner/stats?sessionId=xxx - Get learner mission statistics + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { FirestoreLearnerRepository } from '@/infrastructure/persistence/FirestoreLearnerRepository'; + +const learnerRepo = new FirestoreLearnerRepository(); + +export async function GET(request: NextRequest) { + try { + const sessionId = request.nextUrl.searchParams.get('sessionId'); + + if (!sessionId) { + return NextResponse.json( + { error: 'Session ID required' }, + { status: 400 } + ); + } + + const stats = await learnerRepo.getStatistics(sessionId); + + return NextResponse.json({ stats }); + } catch (error) { + console.error('Failed to get learner stats:', error); + return NextResponse.json( + { error: 'Failed to retrieve statistics' }, + { status: 500 } + ); + } +} diff --git a/mission-authoring/.archived/api-routes-backup/missions/[missionId]/route.ts b/mission-authoring/.archived/api-routes-backup/missions/[missionId]/route.ts new file mode 100644 index 0000000..ecc10ce --- /dev/null +++ b/mission-authoring/.archived/api-routes-backup/missions/[missionId]/route.ts @@ -0,0 +1,58 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getFirestoreInstance } from '@/infrastructure/persistence/firebase-admin'; +import { FirestoreMissionRepository } from '@/infrastructure/persistence/FirestoreMissionRepository'; + +export async function GET( + _request: NextRequest, + { params }: { params: Promise<{ missionId: string }> } +) { + try { + const { missionId } = await params; + console.log(`[API] Fetching mission: ${missionId}`); + + const firestore = getFirestoreInstance(); + const repository = new FirestoreMissionRepository(firestore); + + const mission = await repository.findById(missionId); + + if (!mission) { + console.log(`[API] Mission not found: ${missionId}`); + return NextResponse.json( + { + success: false, + error: 'Mission not found', + }, + { status: 404 } + ); + } + + if (mission.status !== 'completed' || (!mission.videoUrl && !mission.youtubeUrl)) { + console.log(`[API] Mission not public: ${missionId} (status: ${mission.status}, hasVideo: ${!!(mission.videoUrl || mission.youtubeUrl)})`); + return NextResponse.json( + { + success: false, + error: 'Mission not available in public catalogue', + }, + { status: 404 } + ); + } + + console.log(`[API] Returning mission: ${missionId}`); + return NextResponse.json( + { + success: true, + mission, + }, + { status: 200 } + ); + } catch (error) { + console.error('[API] Public mission fetch error:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'Internal server error', + }, + { status: 500 } + ); + } +} diff --git a/mission-authoring/.archived/api-routes-backup/missions/[missionId]/status/route.ts b/mission-authoring/.archived/api-routes-backup/missions/[missionId]/status/route.ts new file mode 100644 index 0000000..2efaf0e --- /dev/null +++ b/mission-authoring/.archived/api-routes-backup/missions/[missionId]/status/route.ts @@ -0,0 +1,111 @@ +/** + * GET /api/missions/[missionId]/status - Mission Status Polling Endpoint (Task 56) + * + * User Story 54: Queue Position Confirmation + * Allows learners to poll mission status with live queue position. + * Returns current status, queue position, and estimated wait time. + * + * Usage Pattern: + * 1. Learner submits mission via POST /api/missions + * 2. Frontend polls this endpoint every 3 seconds + * 3. Queue position updates as missions ahead complete + * 4. Polling stops when mission reaches terminal state (completed/failed/cancelled) + * + * Success Response (200): + * { + * "success": true, + * "mission": { + * "id": "abc123", + * "status": "queued", + * "queuePosition": 3, + * "estimatedWait": 180, // seconds + * "yardId": "uct-rover-1", + * ... + * } + * } + * + * Not Found Response (404): + * { + * "success": false, + * "error": "Mission not found" + * } + * + * Error Response (500): + * { + * "success": false, + * "error": "Internal server error" + * } + */ + +import { NextResponse } from 'next/server'; +import { getFirestoreInstance } from '@/infrastructure/persistence/firebase-admin'; +import { FirestoreMissionRepository } from '@/infrastructure/persistence/FirestoreMissionRepository'; +import { MissionStatusService } from '@/core/application/services/MissionStatusService'; + +/** + * Next.js 16 dynamic route context type + * Params are async in App Router for streaming support + */ +type MissionStatusRouteContext = { + params: Promise<{ + missionId: string; + }>; +}; + +/** + * GET handler for mission status polling + * @param _request - Request object (unused, but required by Next.js signature) + * @param context - Route context containing missionId from URL path + * @returns JSON response with mission status or error + */ +export async function GET(_request: Request, context: MissionStatusRouteContext) { + try { + // Extract missionId from dynamic route params + // Next.js 16+ requires awaiting params for async rendering + const { missionId } = await context.params; + + // Initialize dependencies using dependency injection pattern + // This allows for easy mocking in tests + const firestore = getFirestoreInstance(); + const repository = new FirestoreMissionRepository(firestore); + const service = new MissionStatusService(repository); + + // Fetch mission with live queue position calculation + // Repository computes position by counting queued missions ahead of this one + const mission = await service.getMissionStatus(missionId); + + // Handle mission not found (invalid ID or already deleted) + if (!mission) { + return NextResponse.json( + { + success: false, + error: 'Mission not found', + }, + { status: 404 } + ); + } + + // Return mission with queue position and estimated wait + // queuePosition: number of missions ahead + 1 + // estimatedWait: (queuePosition - 1) * 90 seconds + return NextResponse.json( + { + success: true, + mission, + }, + { status: 200 } + ); + } catch (error) { + // Log error for debugging (appears in Cloud Run logs) + console.error('Mission status fetch error:', error); + + // Return generic error to client (don't expose internal details) + return NextResponse.json( + { + success: false, + error: 'Internal server error', + }, + { status: 500 } + ); + } +} diff --git a/mission-authoring/.archived/api-routes-backup/missions/route.ts b/mission-authoring/.archived/api-routes-backup/missions/route.ts new file mode 100644 index 0000000..dd3362f --- /dev/null +++ b/mission-authoring/.archived/api-routes-backup/missions/route.ts @@ -0,0 +1,41 @@ +import { NextResponse } from 'next/server'; +import { getFirestoreInstance } from '@/infrastructure/persistence/firebase-admin'; +import { FirestoreMissionRepository } from '@/infrastructure/persistence/FirestoreMissionRepository'; + +export async function GET() { + try { + console.log('[API] Fetching missions from Firestore...'); + const firestore = getFirestoreInstance(); + const repository = new FirestoreMissionRepository(firestore); + + const missions = await repository.findAll(); + console.log(`[API] Found ${missions.length} total missions`); + + const publicMissions = missions + .filter((mission) => mission.status === 'completed' && (mission.videoUrl || mission.youtubeUrl)) + .sort((a, b) => { + const aTime = new Date(a.completedAt || a.submittedAt).getTime(); + const bTime = new Date(b.completedAt || b.submittedAt).getTime(); + return bTime - aTime; + }); + + console.log(`[API] Returning ${publicMissions.length} public missions`); + + return NextResponse.json( + { + success: true, + missions: publicMissions, + }, + { status: 200 } + ); + } catch (error) { + console.error('[API] Public missions fetch error:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'Internal server error', + }, + { status: 500 } + ); + } +} diff --git a/mission-authoring/.archived/api-routes-backup/missions/submit/route.ts b/mission-authoring/.archived/api-routes-backup/missions/submit/route.ts new file mode 100644 index 0000000..1c05254 --- /dev/null +++ b/mission-authoring/.archived/api-routes-backup/missions/submit/route.ts @@ -0,0 +1,55 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getFirestoreInstance } from '@/infrastructure/persistence/firebase-admin'; +import { FirestoreMissionRepository } from '@/infrastructure/persistence/FirestoreMissionRepository'; +import { MissionService } from '@/core/application/services/MissionService'; +import { validateMission } from '@/infrastructure/validation/schemas'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + + const validation = validateMission(body); + if (!validation.success) { + return NextResponse.json( + { + success: false, + error: validation.errors?.[0] || 'Validation failed', + }, + { status: 400 } + ); + } + + const firestore = getFirestoreInstance(); + const repository = new FirestoreMissionRepository(firestore); + const service = new MissionService(repository); + + const result = await service.submitMission(validation.data!); + + if (!result.success) { + return NextResponse.json( + { + success: false, + error: result.error || 'Failed to submit mission', + }, + { status: 500 } + ); + } + + return NextResponse.json( + { + success: true, + mission: result.mission, + }, + { status: 201 } + ); + } catch (error) { + console.error('[API] Mission submission error:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'Internal server error', + }, + { status: 500 } + ); + } +} diff --git a/mission-authoring/.archived/api-routes-backup/simulator/execute/route.ts b/mission-authoring/.archived/api-routes-backup/simulator/execute/route.ts new file mode 100644 index 0000000..221b75b --- /dev/null +++ b/mission-authoring/.archived/api-routes-backup/simulator/execute/route.ts @@ -0,0 +1,47 @@ +/** + * POST /api/simulator/execute - Proxy to Simulator Service + * + * Proxies code execution requests to the Python simulator service + * to avoid CORS issues when calling from the browser. + */ + +import { NextRequest, NextResponse } from 'next/server'; + +const SIMULATOR_URL = process.env.SIMULATOR_URL || 'http://localhost:8080'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { code } = body; + + if (!code) { + return NextResponse.json( + { success: false, error: 'No code provided' }, + { status: 400 } + ); + } + + // Forward request to simulator service + const response = await fetch(`${SIMULATOR_URL}/api/execute`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ code }), + }); + + const data = await response.json(); + + return NextResponse.json(data, { status: response.status }); + } catch (error) { + console.error('Simulator proxy error:', error); + return NextResponse.json( + { + success: false, + error: 'Failed to communicate with simulator service', + details: error instanceof Error ? error.message : 'Unknown error', + }, + { status: 500 } + ); + } +} diff --git a/mission-authoring/.gitignore b/mission-authoring/.gitignore new file mode 100644 index 0000000..adf42fd --- /dev/null +++ b/mission-authoring/.gitignore @@ -0,0 +1,49 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem +.DS_Store +**/.DS_Store +.AppleDouble +.LSOverride + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# python cache +__pycache__/ +*.py[cod] \ No newline at end of file diff --git a/mission-authoring/.hintrc b/mission-authoring/.hintrc new file mode 100644 index 0000000..1c11ae1 --- /dev/null +++ b/mission-authoring/.hintrc @@ -0,0 +1,9 @@ +{ + "extends": [ + "development" + ], + "hints": { + "no-inline-styles": "off", + "button-type": "off" + } +} \ No newline at end of file diff --git a/mission-authoring/AGENTS.md b/mission-authoring/AGENTS.md new file mode 100644 index 0000000..8bd0e39 --- /dev/null +++ b/mission-authoring/AGENTS.md @@ -0,0 +1,5 @@ + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + diff --git a/mission-authoring/CLAUDE.md b/mission-authoring/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/mission-authoring/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/mission-authoring/GIT_WORKFLOW.md b/mission-authoring/GIT_WORKFLOW.md new file mode 100644 index 0000000..9304d1c --- /dev/null +++ b/mission-authoring/GIT_WORKFLOW.md @@ -0,0 +1,155 @@ +# Git Workflow for Team + +Guide to using Git in the terminal. +You can copy and paste the commands exactly as shown, or modify. + +## 1. Before You Start + +Open your project folder in the terminal. +First, make sure you are on the latest version of main: + +```bash +git checkout main +git pull origin main +``` + +## 2. Create a New Branch + +Every change must be done on its own branch. +Use these branch types: + +| Type | When to Use | +|------|-------------| +| `feat/` | New features | +| `fix/` | Bug fixes | +| `chore/` | Cleanup, refactoring, config changes | + +### Examples + +```bash +git checkout -b feat/login-page +git checkout -b fix/navbar-bug +git checkout -b chore/update-dependencies +``` + +## 3. Make Your Changes + +Edit your files normally in VS Code or your IDE. + +## 4. Check What Changed + +```bash +git status +``` + +This shows: +- modified files +- new files +- deleted files + +## 5. Save Your Changes to Git + +### Stage all files + +```bash +git add . +``` + +### Create a commit + +```bash +git commit -m "feat: add login validation" +``` + +## 6. Push Your Branch to Azure DevOps + +Replace the branch name with your own. + +```bash +git push -u origin feat/login-page +``` + +### Example: + +```bash +git push -u origin fix/navbar-bug +``` + +## 7. Create a Pull Request (PR) in Azure DevOps + +After pushing: + +1. Go to: + - Repos + - Pull Requests +2. Click **New Pull Request** +3. Set: + - Source branch: your branch + - Target branch: main +4. Add: + - a clear title + - short description +5. Add reviewers +6. Click **Create** + +## 8. After Review + +If reviewers request changes: + +1. Make the changes +2. Run: + ```bash + git add . + git commit -m "fix: address PR comments" + git push + ``` + Your PR updates automatically. + +## 9. Merge the Pull Request + +Once approvals are complete and checks pass: + +1. Open the PR +2. Click **Complete / Merge** +3. Delete the branch after merging if prompted + +## Commit Message Examples + +```bash +git commit -m "feat: add user profile page" +git commit -m "fix: resolve login redirect issue" +git commit -m "chore: clean up unused imports" +``` + +## Full Example Workflow + +```bash +# Get latest main branch +git checkout main +git pull origin main + +# Create branch +git checkout -b feat/login-page + +# Check changes +git status + +# Stage changes +git add . + +# Commit changes +git commit -m "feat: add login page" + +# Push branch +git push -u origin feat/login-page +``` + +Then create the Pull Request in Azure DevOps. + +## Important Rules + +- Never push directly to main +- Always create a new branch first +- Every change must go through a Pull Request +- At least 1 other teammate must review before merging +- Keep branch names short and descriptive diff --git a/mission-authoring/README.md b/mission-authoring/README.md new file mode 100644 index 0000000..91c86f8 --- /dev/null +++ b/mission-authoring/README.md @@ -0,0 +1,83 @@ +# Mars Rover Mission Authoring + +A learner-focused platform for authoring and testing Mars rover missions using visual block programming and text editing. + +## Prerequisites + +- Node.js 18+ +- npm 9+ +- Firebase project with Firestore enabled + +## Installation + +1. Clone the repository: +``` +Fastest way is to use Azure's cloning to your IDE, you can use git clone but you have to generate credentials in Azure when cloning. +cd mars-rover-mission-authoring +``` + +2. Install dependencies: +```bash +npm install +``` + +3. Set up environment variables: +``` +.env file is committed with the firebase App config. so you can skip step 4 unless reconfiguring. +``` + +4. Fill in your Firebase configuration in `.env`. **Be careful not to commit real Firebase keys or credentials to the repository:** +```env +NEXT_PUBLIC_FIREBASE_API_KEY=your_client_api_key +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com +NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id +NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.firebasestorage.app +NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_client_messaging_sender_id +NEXT_PUBLIC_FIREBASE_APP_ID=your_client_app_id +NEXT_PUBLIC_SIMULATOR_API_URL=http://localhost:8080 +``` + + +## Running Locally + +Start the development server (runs both web and simulator): +```bash +npm run dev +``` + +The web app will be available at `http://localhost:3000` and the simulator API at `http://localhost:8080`. + +## Project Structure + +- `src/app/` - Next.js pages and routes +- `src/components/` - React components (editors, mission workspace, simulator) +- `src/contexts/` - React context providers (authentication, learner tracking) +- `src/infrastructure/` - Data persistence and external services +- `src/hooks/` - Custom React hooks for mission submission and learner state +- `simulator-service/` - Flask-based rover physics simulator + +## How It Works + +1. Learners create missions using the visual block editor (Blockly) or text editor (Monaco) +2. Missions are submitted to Firebase Firestore with normalized code and auto-generated IDs +3. Learner mission history is tracked per browser session +4. When executed, mission code runs against the rover simulator +5. Results are displayed in the mission workspace + +## Learn More + +- Check out [docs/](docs/) for guides on mission history, learner tracking, and validation +- See the mission workspace at `/mission` and mission list at `/missions` + +## Editors + +- **Visual Block Editor:** A Blockly-based drag-and-drop editor for building missions visually inside the mission workspace. Ideal for learners who prefer visual programming. +- **Text Editor:** A Monaco-based text editor for editing mission code directly (JavaScript/JSON). You can switch between editors when creating or editing a mission. + +## Mission Catalogue + +- The mission catalogue (available at `/missions`) lists published missions with metadata (difficulty, tags, thumbnail) and actions to run or edit missions. Missions are persisted in Firestore so instructors and learners can browse and reuse authored content. + +## Mission History + +- Learner mission history tracks submissions, outcomes, and timestamps for each learner or session. History is viewable in the mission history UI and is stored in Firestore to enable replay, review, and export of past attempts. diff --git a/mission-authoring/docs/CODE_VALIDATION_SYSTEM.md b/mission-authoring/docs/CODE_VALIDATION_SYSTEM.md new file mode 100644 index 0000000..80955fc --- /dev/null +++ b/mission-authoring/docs/CODE_VALIDATION_SYSTEM.md @@ -0,0 +1,325 @@ +# Code Validation System Documentation + +## Overview + +The Mars Yard platform validates rover code at multiple layers for security and safety. + +## Validation Layers + +### Layer 1: Client-Side Pre-Validation (TypeScript) +**Location**: [`src/infrastructure/sandbox/ast-allowlist-analyzer.ts`](../src/infrastructure/sandbox/ast-allowlist-analyzer.ts) + +**Purpose**: Fast feedback before submission + +**Checks**: +- Disallowed imports (os, sys, subprocess, etc.) +- Non-rover function calls +- Dangerous built-ins (eval, exec, open, etc.) + +**Status**: ✅ Working - Regex-based pattern matching + +### Layer 2: Server-Side Validation (Next.js API) +**Location**: [`src/infrastructure/validation/schemas.ts`](../src/infrastructure/validation/schemas.ts) + +**Purpose**: Secure validation before queuing + +**Checks**: +- Schema validation (Zod) +- Allowlist validation (same as Layer 1) +- Code length limits +- Required fields + +**Status**: ✅ Working + +### Layer 3: Python Simulator Validation +**Location**: `simulator-service/simulator_api.py` + +**Purpose**: Final validation before execution + +**Checks**: +- Python syntax validation +- Runtime safety checks +- Resource limits + +**Status**: ✅ Working + +## Supported Rover Commands + +### JavaScript/TypeScript Syntax (Preferred) +```javascript +rover.forward(distance, speed) +rover.reverse(distance, speed) +rover.spinLeft(angle, speed) +rover.spinRight(angle, speed) +rover.steerLeft(distance, speed) +rover.steerRight(distance, speed) +rover.stop() +rover.wait(seconds) +``` + +### Python Syntax (Also Supported) +```python +rover.forward(distance) +rover.backward(distance) +rover.turn_left(degrees) +rover.turn_right(degrees) +rover.spinLeft(degrees) +rover.spinRight(degrees) +rover.wait(seconds) +rover.stop() +``` + +## Allowlist Configuration + +**File**: [`src/infrastructure/sandbox/rover-command-allowlist.ts`](../src/infrastructure/sandbox/rover-command-allowlist.ts) + +**Allowed commands**: +```typescript +[ + 'rover.forward', + 'rover.backward', + 'rover.reverse', + 'rover.turn_left', + 'rover.turn_right', + 'rover.spinLeft', + 'rover.spinRight', + 'rover.steerLeft', + 'rover.steerRight', + 'rover.wait', + 'rover.stop', + 'rover.get_distance', + 'rover.get_heading', +] +``` + +## Code Format Support + +### ✅ Multiline Commands +```javascript +rover.forward(80, 5) +rover.spinRight(60, 1) +rover.forward(80, 5) +``` + +### ✅ Newline-Separated +```javascript +rover.forward(100) +rover.spinLeft(90) +``` + +### ✅ With Semicolons (Optional) +```javascript +rover.forward(80, 5); +rover.spinRight(60, 1); +``` + +### ✅ Mixed Spacing +```javascript +rover.forward(80, 5) + +rover.spinRight(60, 1) + +rover.forward(80, 5) +``` + +## Validation Algorithm + +### Function Call Detection +**Regex**: `/rover\.(\w+)\s*\(/g` + +**Matches**: +- `rover.forward(` ✅ +- `rover.spinRight (` ✅ +- `rover.stop()` ✅ + +**Does NOT match**: +- Comments: `// rover.forward()` +- Strings: `"rover.forward()"` + +### Import Detection +**Regex**: `/^\s*import\s+(\w+)/` + +**Blocks**: +```python +import os # ❌ Blocked +import sys # ❌ Blocked +from subprocess import run # ❌ Blocked +``` + +## Error Messages + +### Disallowed Import +``` +code (line 1): Import of 'os' is not allowed. Only rover commands are permitted. +``` + +### Disallowed Function +``` +code (line 3): Function 'rover.hack_mainframe' is not in the approved rover command list. +``` + +### Dangerous Builtin +``` +code (line 5): Built-in function 'eval' is not allowed for safety reasons. +``` + +## Testing Validation + +### Test 1: Valid Code +```typescript +const code = ` +rover.forward(80, 5) +rover.spinRight(60, 1) +rover.forward(80, 5) +`; + +// Should pass all validations +``` + +### Test 2: Invalid Import +```python +import os +rover.forward(100) +``` +**Expected**: ❌ Blocked at Layer 1 & 2 + +### Test 3: Invalid Function +```javascript +rover.hackTheGibson() +``` +**Expected**: ❌ Blocked at Layer 1 & 2 + +## Common Issues + +### Issue: "Code validation failed" from simulator + +**Cause**: Python simulator rejects code for syntax or runtime reasons + +**Debug**: +1. Check browser console for detailed error +2. Test code directly with curl: +```bash +curl -X POST http://localhost:8080/api/execute \ + -H "Content-Type: application/json" \ + -d '{"code":"rover.forward(100)"}' +``` +3. Check simulator logs + +### Issue: Code passes TypeScript validation but fails Python + +**Cause**: TypeScript uses regex patterns; Python uses full AST parsing + +**Solution**: Python simulator is authoritative - update TypeScript patterns to match + +### Issue: Valid command marked as invalid + +**Cause**: Command not in allowlist + +**Solution**: Add to [`ROVER_COMMAND_ALLOWLIST`](../src/infrastructure/sandbox/rover-command-allowlist.ts) + +## Extending the Allowlist + +### Adding a New Command + +1. **Update allowlist**: +```typescript +// src/infrastructure/sandbox/rover-command-allowlist.ts +export const ROVER_COMMAND_ALLOWLIST = [ + // ... existing commands + 'rover.new_command', // Add here +]; +``` + +2. **Update tests**: +```typescript +// src/__tests__/unit/allowlist.test.ts +it('should allow new command', () => { + const code = 'rover.new_command(100)'; + const result = allowlistService.analyze(code); + expect(result.isValid).toBe(true); +}); +``` + +3. **Update documentation**: +- Add to this file +- Add to user-facing docs + +## Security Considerations + +### Why Pattern Matching? + +**Pros**: +- Fast (no Python runtime needed) +- Works in browser +- Catches 99% of violations + +**Cons**: +- Can be bypassed with obfuscation +- Not a full AST parser + +**Mitigation**: +- Server-side validation (Layer 2) +- Python sandbox (Layer 3) +- Fail-closed approach + +### Defense in Depth + +All three layers must agree: +1. Client says: "Code looks safe" +2. Server says: "Code is definitely safe" +3. Python says: "Code executed safely" + +If any layer rejects, the code doesn't run. + +## Performance + +- **Layer 1**: ~1ms (regex matching) +- **Layer 2**: ~5ms (Zod + regex) +- **Layer 3**: ~50-200ms (Python execution) + +## Monitoring + +### Validation Failures + +Track in logs: +- Which layer rejected +- Violation type +- Line number +- User session (for abuse detection) + +### False Positives + +If valid code is rejected: +1. Check allowlist +2. Check regex patterns +3. File bug report with code sample + +## Related Files + +- [`src/infrastructure/sandbox/rover-command-allowlist.ts`](../src/infrastructure/sandbox/rover-command-allowlist.ts) - Allowlist definition +- [`src/infrastructure/sandbox/ast-allowlist-analyzer.ts`](../src/infrastructure/sandbox/ast-allowlist-analyzer.ts) - Pattern matching logic +- [`src/core/application/services/AllowlistService.ts`](../src/core/application/services/AllowlistService.ts) - Service layer +- [`src/infrastructure/validation/schemas.ts`](../src/infrastructure/validation/schemas.ts) - Zod schemas +- [`src/__tests__/unit/allowlist.test.ts`](../src/__tests__/unit/allowlist.test.ts) - Unit tests + +## Summary + +The validation system is **already working correctly** for the code format you're using: + +```javascript +rover.forward(80, 5) +rover.spinRight(60, 1) +rover.forward(80, 5) +``` + +✅ Passes TypeScript validation +✅ Passes Next.js API validation +✅ Passes Python simulator validation +✅ Executes successfully + +If you're seeing "Code validation failed", it's likely: +1. A different code snippet with actual violations +2. A caching issue (clear browser cache) +3. A simulator service error (check logs) + +The validation system supports multiline, newline-separated commands with optional semicolons, exactly as specified in your requirements. diff --git a/mission-authoring/docs/MISSION_HISTORY_UPDATE_SUMMARY.md b/mission-authoring/docs/MISSION_HISTORY_UPDATE_SUMMARY.md new file mode 100644 index 0000000..a670506 --- /dev/null +++ b/mission-authoring/docs/MISSION_HISTORY_UPDATE_SUMMARY.md @@ -0,0 +1,358 @@ +# Mission History Feature Update Summary + +## Overview + +Updated the Mission History feature to support real-time operator feedback and YouTube video links for anonymous learners. + +## Problem Solved + +Previously, learners had no way to see: +- Real-time status updates after submitting a mission +- Execution results and feedback from operators +- Video recordings of their mission execution + +## Solution + +Implemented a dual-storage system: +1. **Main missions collection**: `missions/{missionId}` - For operator queue (existing) +2. **Learner subcollection**: `learners/{learnerId}/missions/{missionId}` - For learner history (new) + +This enables: +- Real-time updates via Firestore `onSnapshot` listeners +- Operator feedback to sync instantly to learner UI +- Anonymous learner tracking via `learnerId` from localStorage + +## Files Created + +### 1. Learner Mission Service +**File**: `src/lib/services/learnerMissionService.ts` + +Core service for managing learner-specific missions: +- `saveLearnerMission()` - Save/update mission in learner subcollection +- `getLearnerMission()` - Get single mission +- `getLearnerMissions()` - Get all missions for a learner +- `subscribeLearnerMissions()` - Real-time listener for instant updates +- `updateLearnerMission()` - Update mission status/feedback + +**Key Feature**: Uses Firestore `onSnapshot` for real-time updates. + +### 2. Operator Mission Service +**File**: `src/lib/services/operatorMissionService.ts` + +Helper functions for operators: +- `markMissionInProgress()` - Update status when starting execution +- `completeMission()` - Mark complete with video link and notes +- `completeMissionWithValidation()` - With YouTube URL validation +- `isValidYouTubeUrl()` - Validate YouTube URLs + +### 3. Mission Submit Hook +**File**: `src/hooks/useMissionSubmit.ts` + +React hook for learner mission submission: +- Submits to main missions API (operator queue) +- Saves to learner subcollection (history) +- Handles errors gracefully +- Returns submission state + +**Usage**: +```typescript +const { submitMission, isSubmitting, error } = useMissionSubmit(); + +await submitMission({ + code: '...', + yardId: 'yard-1', + sessionId: 'session-123', + challengeId: 'M1-FORWARD', + missionName: 'My First Mission', +}); +``` + +### 4. Documentation +**Files**: +- `docs/OPERATOR_MISSION_UPDATE.md` - Complete operator guide +- `docs/MISSION_HISTORY_UPDATE_SUMMARY.md` - This file + +## Files Modified + +### Mission History Scaffold Component +**File**: `src/components/mission/MissionHistoryScaffold.tsx` + +**Changes**: +1. ✅ Uses `getLearnerID()` for anonymous learner tracking +2. ✅ Implements real-time updates via `subscribeLearnerMissions()` +3. ✅ Displays new status values: `submitted`, `in-progress`, `completed` +4. ✅ Shows YouTube video embed when operator completes mission +5. ✅ Displays operator execution notes +6. ✅ User-friendly status messages for each state +7. ✅ Handles missions without video links gracefully +8. ✅ Auto-cleanup of listener on component unmount + +**Status Messages**: +- `submitted` → "Waiting for operator execution" +- `in-progress` → "Being executed on rover" +- `completed` → "Execution completed" + video + notes + +## Data Structure + +### LearnerMission Interface + +```typescript +interface LearnerMission { + missionId: string; // Unique mission identifier + missionName?: string; // Optional display name + status: 'submitted' | 'in-progress' | 'completed'; + submittedAt: string; // ISO timestamp + completedAt?: string; // ISO timestamp (when completed) + youtubeVideoLink?: string; // YouTube URL from operator + executionNotes?: string; // Operator feedback + code?: string; // Mission code (optional) + yardId?: string; // Rover yard ID + challengeId?: string; // Challenge/level ID +} +``` + +### Firestore Structure + +``` +learners/ + {learnerId}/ + missions/ + {missionId}/ + - missionId: "abc123" + - missionName: "First Mission" + - status: "completed" + - submittedAt: "2026-04-24T10:00:00Z" + - completedAt: "2026-04-24T10:05:00Z" + - youtubeVideoLink: "https://youtube.com/watch?v=..." + - executionNotes: "Great job!" + - updatedAt: "2026-04-24T10:05:00Z" +``` + +## Mission Flow + +### Learner Side + +``` +1. Learner writes code +2. Submits mission via useMissionSubmit() + ├─> POST /api/missions (operator queue) + └─> saveLearnerMission() (learner history) +3. Status: "submitted" +4. Learner opens /history page +5. Real-time listener activates +6. Sees "Waiting for operator execution" +``` + +### Operator Side + +``` +1. Operator sees mission in queue +2. Starts execution + └─> markMissionInProgress(learnerId, missionId) +3. Learner sees "Being executed on rover" (instant) +4. Operator finishes execution +5. Uploads video to YouTube +6. Marks complete + └─> completeMission(learnerId, missionId, youtubeUrl, notes) +7. Learner sees video and notes (instant) +``` + +## Real-Time Updates + +The system uses Firestore `onSnapshot` for instant updates: + +```typescript +// In MissionHistoryScaffold.tsx +const unsubscribe = subscribeLearnerMissions(learnerId, (missions) => { + setMissions(missions); // UI updates instantly +}); +``` + +**Benefits**: +- No polling required +- Sub-second latency +- Automatic reconnection +- Efficient bandwidth usage + +## Integration Points + +### For Frontend Developers + +Use the mission submit hook: +```typescript +import { useMissionSubmit } from '@/hooks/useMissionSubmit'; + +const { submitMission, isSubmitting } = useMissionSubmit(); +``` + +### For Operator Systems + +Use the operator service: +```typescript +import { + markMissionInProgress, + completeMission, +} from '@/lib/services/operatorMissionService'; + +// When starting +await markMissionInProgress(learnerId, missionId); + +// When done +await completeMission( + learnerId, + missionId, + 'https://youtube.com/watch?v=VIDEO_ID', + 'Excellent work!' +); +``` + +### For Backend Services + +Use the learner mission service: +```typescript +import { + saveLearnerMission, + updateLearnerMission, +} from '@/lib/services/learnerMissionService'; +``` + +## Testing + +### Manual Testing Steps + +1. **Submission Flow**: + - Submit a mission as learner + - Verify it appears in `/history` with "submitted" status + - Check Firestore: `learners/{learnerId}/missions/{missionId}` exists + +2. **Status Update Flow**: + - Open `/history` in browser tab + - Update mission status in Firestore console + - Verify UI updates instantly without refresh + +3. **Video Display Flow**: + - Update mission with `youtubeVideoLink` + - Verify video embeds correctly + - Test different YouTube URL formats + +### Automated Testing + +Recommended test cases: +- Mission submission saves to both collections +- Real-time listener receives updates +- YouTube URL validation works +- Empty state displays correctly +- Component cleanup prevents memory leaks + +## Backward Compatibility + +✅ **No Breaking Changes** + +- Existing missions collection unchanged +- Existing submission API unchanged +- New learner subcollection is additive +- Old missions won't break (just won't have learner history) + +## Performance Considerations + +- **Firestore Reads**: One onSnapshot listener per learner (efficient) +- **Firestore Writes**: Two writes per submission (main + learner) +- **Real-time**: Uses WebSocket, minimal overhead +- **Cleanup**: Listener unsubscribed on component unmount + +## Security Considerations + +1. **Anonymous Tracking**: Uses localStorage-based learnerId +2. **Data Privacy**: No personal information stored +3. **YouTube URLs**: Validated before storage +4. **Firestore Rules**: Need to ensure learners can only read their own missions + +### Recommended Firestore Rules + +```javascript +match /learners/{learnerId}/missions/{missionId} { + // Learners can read their own missions + allow read: if request.auth == null; // Anonymous allowed + + // Only operators can write + allow write: if request.auth != null + && request.auth.token.role == 'operator'; +} +``` + +## Future Enhancements + +Potential improvements: +- [ ] Mission completion notifications +- [ ] Email digest of completed missions +- [ ] Mission ratings and feedback from learners +- [ ] Video thumbnail generation +- [ ] Mission replay functionality +- [ ] Operator performance metrics + +## Migration Strategy + +For existing missions: + +```typescript +// One-time migration script +async function migrateMissionsToLearnerSubcollection() { + const missions = await getAllMissions(); + + for (const mission of missions) { + const learnerId = mission.sessionId; // sessionId IS learnerId + + await saveLearnerMission(learnerId, { + missionId: mission.id, + status: mission.status === 'queued' ? 'submitted' : + mission.status === 'processing' ? 'in-progress' : 'completed', + submittedAt: mission.submittedAt, + completedAt: mission.completedAt, + code: mission.code, + yardId: mission.yardId, + challengeId: mission.challengeId, + }); + } +} +``` + +## Troubleshooting + +### Issue: Real-time updates not working + +**Solutions**: +- Check browser console for Firestore errors +- Verify Firebase config in `.env` +- Ensure listener is active (check component is mounted) +- Check Firestore rules allow read access + +### Issue: Video not displaying + +**Solutions**: +- Verify YouTube URL format +- Check video is public/unlisted (not private) +- Inspect `youtubeVideoLink` field in Firestore +- Check VideoPlayer component props + +### Issue: Mission not appearing in history + +**Solutions**: +- Verify `learnerId` matches localStorage value +- Check mission was saved to correct subcollection path +- Inspect Firestore data structure +- Check for JavaScript errors in submission flow + +## Summary + +This update successfully implements: + +✅ Real-time mission status updates for learners +✅ Operator feedback sync (video links + notes) +✅ Anonymous learner support via learnerId +✅ Dual-storage system (queue + history) +✅ Firestore onSnapshot for instant updates +✅ Clean separation of concerns (services, hooks, components) +✅ Comprehensive documentation for operators +✅ Backward compatible with existing system + +The Mission History page now provides a complete feedback loop between learners and operators, enabling effective learning through video review and personalized feedback. diff --git a/mission-authoring/docs/OPERATOR_MISSION_UPDATE.md b/mission-authoring/docs/OPERATOR_MISSION_UPDATE.md new file mode 100644 index 0000000..0bca65d --- /dev/null +++ b/mission-authoring/docs/OPERATOR_MISSION_UPDATE.md @@ -0,0 +1,212 @@ +# Operator Mission Update Guide + +This guide explains how operators can update missions after execution to provide feedback and video links. + +## Overview + +Missions are stored in a single collection: `missions/{missionId}` with a `learnerId` field. + +This unified storage enables: +- Real-time updates that appear instantly in the learner's Mission History page +- Direct access to missions by `learnerId` +- Simplified data model with no subcollections + +## Mission Status Flow + +``` +queued → processing → completed +``` + +- **queued**: Mission is waiting in the operator's queue +- **processing**: Operator has started execution on rover +- **completed**: Execution finished, video and YouTube link added +- **failed**: Execution encountered an error +- **cancelled**: Mission was cancelled + +## Updating Mission Status + +### Method 1: Direct Firestore Update (Recommended) + +```typescript +import { doc, updateDoc } from 'firebase/firestore'; +import { getFirestoreClient } from '@/lib/firebase'; + +const db = getFirestoreClient(); +const missionRef = doc(db, 'missions', missionId); + +// When starting execution +await updateDoc(missionRef, { + status: 'processing', + startedAt: new Date().toISOString(), +}); + +// When execution completes +await updateDoc(missionRef, { + status: 'completed', + completedAt: new Date().toISOString(), + youtubeUrl: 'https://www.youtube.com/watch?v=VIDEO_ID', +}); +``` + +## Required Fields for Completion + +When marking a mission as **completed**, include: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `status` | string | ✅ Yes | Set to `"completed"` | +| `completedAt` | string (ISO) | ✅ Yes | Timestamp when execution finished | +| `youtubeUrl` | string | ⚠️ Recommended | YouTube video URL for playback | +| `startedAt` | string (ISO) | ⚠️ Recommended | Timestamp when execution started | + +## YouTube Video Link Format + +Accepted formats: +- `https://www.youtube.com/watch?v=VIDEO_ID` +- `https://youtu.be/VIDEO_ID` +- `https://www.youtube.com/embed/VIDEO_ID` + +The video will be automatically embedded in the learner's Mission History page. + +## Example: Complete Operator Flow + +```typescript +import { doc, updateDoc, getDoc } from 'firebase/firestore'; +import { getFirestoreClient } from '@/lib/firebase'; + +// 1. Operator receives mission from queue +const mission = await getNextMissionFromQueue(); +const missionId = mission.id; + +// 2. Mark as processing +const db = getFirestoreClient(); +const missionRef = doc(db, 'missions', missionId); + +await updateDoc(missionRef, { + status: 'processing', + startedAt: new Date().toISOString(), +}); + +// 3. Execute mission on rover +const executionResult = await executeOnRover(mission.code); + +// 4. Upload video to YouTube +const youtubeLink = await uploadToYouTube(executionResult.videoFile); + +// 5. Mark as completed with video link +await updateDoc(missionRef, { + status: 'completed', + completedAt: new Date().toISOString(), + youtubeUrl: youtubeLink, + executionResult: { + isSuccessful: executionResult.success, + consoleOutput: executionResult.output, + errorMessage: executionResult.error, + }, +}); +``` + +## Real-Time Updates + +The learner's Mission History page uses Firestore `onSnapshot` listeners on the missions collection. Updates appear **instantly** without page refresh: + +1. Learner sees "Waiting in queue for operator execution" +2. Operator updates status to `processing` +3. Learner sees "Being executed on rover" (instant update) +4. Operator completes and adds video link +5. Learner sees video (instant update) + +## Finding the Mission + +All missions have a `learnerId` field that identifies which learner submitted it: + +```typescript +// Get a mission by ID +const missionDoc = await getDoc(doc(db, 'missions', missionId)); +const mission = missionDoc.data(); + +// The mission has a learnerId field for reference +console.log(mission.learnerId); // Unique learner identifier +``` + +## Data Integrity + +**Important**: Never overwrite the entire mission document. Always use `updateDoc` with partial updates to preserve existing data: + +```typescript +// ✅ GOOD: Partial update +await updateDoc(missionRef, { + status: 'completed', + youtubeUrl: 'https://youtube.com/watch?v=...', +}); + +// ❌ BAD: Overwrites entire document +await setDoc(missionRef, { + status: 'completed', + youtubeUrl: 'https://youtube.com/watch?v=...', +}); +``` + +## Troubleshooting + +### Mission not appearing in learner history + +1. Verify the mission was saved to `missions/{missionId}` with correct `learnerId` +2. Check that the mission's `learnerId` matches the learner's localStorage ID +3. Ensure Firestore rules allow read/write access +4. Verify Firestore index exists for: `missions(learnerId ASC, submittedAt DESC)` + +### Video not displaying + +1. Verify YouTube URL format is correct +2. Check that video is public or unlisted (not private) +3. Ensure `youtubeUrl` field name is spelled correctly (not `youtubeVideoLink`) +4. Check browser console for player errors + +### Real-time updates not working + +1. Verify Firestore onSnapshot listener is active on missions collection +2. Check browser console for Firestore errors +3. Ensure learner is on the `/history` page +4. Verify mission has `learnerId` field matching current learner + +## API Integration + +For automated operator systems, create an API endpoint: + +```typescript +// app/api/operator/missions/[missionId]/complete/route.ts +import { doc, updateDoc } from 'firebase/firestore'; +import { getFirestoreInstance } from '@/infrastructure/persistence/firebase-admin'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function POST( + request: NextRequest, + { params }: { params: { missionId: string } } +) { + const { youtubeUrl, startedAt, completedAt } = await request.json(); + const db = getFirestoreInstance(); + + const missionRef = doc(db, 'missions', params.missionId); + + await updateDoc(missionRef, { + status: 'completed', + youtubeUrl, + startedAt, + completedAt: completedAt || new Date().toISOString(), + }); + + return NextResponse.json({ success: true }); +} +``` + +## Security Considerations + +- Operators should validate YouTube URLs before saving +- Only authorized operators should be able to update missions +- Validate `status` field is one of: queued, processing, completed, failed, cancelled +- Consider adding operator identity tracking in execution result + +## Deprecated + +The `learnerMissionService` and learner subcollection pattern (`learners/{learnerId}/missions/{missionId}`) are deprecated. Use direct mission updates instead. diff --git a/mission-authoring/docs/QUICK_START_MISSION_HISTORY.md b/mission-authoring/docs/QUICK_START_MISSION_HISTORY.md new file mode 100644 index 0000000..90c9d87 --- /dev/null +++ b/mission-authoring/docs/QUICK_START_MISSION_HISTORY.md @@ -0,0 +1,193 @@ +# Mission History Quick Start Guide + +Quick reference for using the Mission History feature with the flattened data model. + +## For Learners (Frontend) + +### Viewing Mission History + +Navigate to `/history` page. Missions automatically update in real-time via Firestore `onSnapshot` listener. + +### Submitting a Mission with History Tracking + +```typescript +import { useMissionSubmit } from '@/hooks/useMissionSubmit'; +import { getLearnerID } from '@/lib/getLearnerID'; + +function MyComponent() { + const { submitMission, isSubmitting, error } = useMissionSubmit(); + + const handleSubmit = async () => { + const result = await submitMission({ + code: 'rover.forward(100)', + yardId: 'uct-rover-1', + learnerId: getLearnerID(), + sessionId: getLearnerID(), + challengeId: 'M1-FORWARD', + missionName: 'My First Mission', + }); + + if (result.success) { + console.log('Mission submitted:', result.missionId); + // Redirect to history page + router.push('/history'); + } else { + console.error('Submission failed:', result.error); + } + }; + + return ( + + ); +} +``` + +## For Operators + +### Direct Firestore Update (Recommended) + +```typescript +import { doc, updateDoc } from 'firebase/firestore'; +import { getFirestoreClient } from '@/lib/firebase'; + +const db = getFirestoreClient(); + +// When starting execution +await updateDoc(doc(db, 'missions', missionId), { + status: 'processing', + startedAt: new Date().toISOString(), +}); + +// When finished with video +await updateDoc(doc(db, 'missions', missionId), { + status: 'completed', + completedAt: new Date().toISOString(), + youtubeUrl: 'https://www.youtube.com/watch?v=ABC123', +}); +``` + +### Via API Endpoint + +```typescript +// POST /api/operator/missions/[missionId]/complete +const response = await fetch('/api/operator/missions/abc123/complete', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + youtubeUrl: 'https://youtube.com/watch?v=ABC123', + completedAt: new Date().toISOString(), + }), +}); +``` + +## Mission Status Values + +| Status | When | Visible To Learner | +|--------|------|-------------------| +| `submitted` | Learner submits mission | "Waiting for operator execution" | +| `in-progress` | Operator starts execution | "Being executed on rover" | +| `completed` | Operator finishes + adds video | Video player + execution notes | + +## Data Structure + +```typescript +interface LearnerMission { + missionId: string; + missionName?: string; + status: 'submitted' | 'in-progress' | 'completed'; + submittedAt: string; // ISO timestamp + completedAt?: string; // ISO timestamp + youtubeVideoLink?: string; // Full YouTube URL + executionNotes?: string; // Operator feedback + code?: string; + yardId?: string; + challengeId?: string; +} +``` + +## Firestore Paths + +``` +learners/{learnerId}/missions/{missionId} +``` + +Example: +``` +learners/abc123xyz/missions/mission-001/ + ├─ missionId: "mission-001" + ├─ status: "completed" + ├─ submittedAt: "2026-04-24T10:00:00Z" + ├─ completedAt: "2026-04-24T10:05:00Z" + ├─ youtubeVideoLink: "https://youtube.com/watch?v=..." + └─ executionNotes: "Great work!" +``` + +## Real-Time Updates + +The `/history` page automatically subscribes to mission updates: + +```typescript +// This happens automatically in MissionHistoryScaffold +const unsubscribe = subscribeLearnerMissions(learnerId, (missions) => { + // UI updates instantly when operator changes status + setMissions(missions); +}); +``` + +## Testing Locally + +1. Submit a mission as a learner +2. Open Firestore console +3. Navigate to: `learners/{learnerId}/missions/{missionId}` +4. Manually update `status` to `"in-progress"` +5. Watch the UI update instantly +6. Add `youtubeVideoLink` field +7. Watch video appear instantly + +## Common Patterns + +### Get Learner ID +```typescript +import { getLearnerID } from '@/lib/getLearnerID'; +const learnerId = getLearnerID(); // Returns or creates ID +``` + +### Check if YouTube URL is Valid +```typescript +import { isValidYouTubeUrl } from '@/lib/services/operatorMissionService'; + +if (!isValidYouTubeUrl(url)) { + alert('Invalid YouTube URL'); +} +``` + +### Save Mission Manually +```typescript +import { saveLearnerMission } from '@/lib/services/learnerMissionService'; + +await saveLearnerMission(learnerId, { + missionId: 'mission-001', + status: 'submitted', + submittedAt: new Date().toISOString(), + code: 'rover.forward(100)', +}); +``` + +## Error Handling + +```typescript +try { + await completeMission(learnerId, missionId, youtubeUrl, notes); +} catch (error) { + console.error('Failed to complete mission:', error); + // Show error to operator +} +``` + +## See Also + +- [Full Operator Guide](./OPERATOR_MISSION_UPDATE.md) +- [Complete Update Summary](./MISSION_HISTORY_UPDATE_SUMMARY.md) +- [MissionHistoryScaffold Component](../src/components/mission/MissionHistoryScaffold.tsx) diff --git a/mission-authoring/docs/TROUBLESHOOTING_MISSION_HISTORY.md b/mission-authoring/docs/TROUBLESHOOTING_MISSION_HISTORY.md new file mode 100644 index 0000000..28447a8 --- /dev/null +++ b/mission-authoring/docs/TROUBLESHOOTING_MISSION_HISTORY.md @@ -0,0 +1,253 @@ +# Troubleshooting Mission History + +## Issue: Missions not appearing in history after submission + +### Root Cause +The mission history page looks for missions in the **learner subcollection** (`learners/{learnerId}/missions`), but missions also need to be saved to the **main missions collection** (`missions/{missionId}`) for the operator queue. + +### Solution Applied +Updated `MissionWorkspaceScaffold.tsx` to save missions to BOTH locations: +1. Main collection (for operators) +2. Learner subcollection (for history) + +### How to Verify It's Working + +#### Step 1: Check learnerId +Open browser console and run: +```javascript +localStorage.getItem('mars-rover-learner-id') +``` +You should see a unique ID like `abcd1234xyz`. This is your learnerId. + +#### Step 2: Submit a mission +1. Go to the mission workspace +2. Write some code (e.g., `rover.forward(100)`) +3. Click "Send to Rover Queue" +4. Check console for these messages: + - ✅ Mission submitted: {mission object} + - ✅ Mission saved to learner history + +#### Step 3: Check Firestore +Open Firebase Console → Firestore Database + +**Main collection** (for operators): +``` +missions/ + {missionId}/ + ├─ id: "abc123" + ├─ code: "rover.forward(100)" + ├─ sessionId: "your-learner-id" + ├─ status: "queued" + └─ yardId: "uct-rover-1" +``` + +**Learner subcollection** (for history): +``` +learners/ + {your-learner-id}/ + missions/ + {missionId}/ + ├─ missionId: "abc123" + ├─ missionName: "Mission abc123" + ├─ status: "submitted" + ├─ code: "rover.forward(100)" + ├─ submittedAt: "2026-04-24T..." + └─ yardId: "uct-rover-1" +``` + +#### Step 4: Check Mission History Page +1. Navigate to `/history` +2. You should see your submitted mission +3. Status should show: "Waiting for operator execution" + +### Common Issues + +#### Issue: "No missions yet" on history page + +**Check 1**: Verify learnerId consistency +```javascript +// In browser console +localStorage.getItem('mars-rover-learner-id') +``` + +**Check 2**: Verify mission was saved to Firestore +- Go to Firebase Console +- Navigate to: `learners/{your-learner-id}/missions` +- Check if missions exist + +**Check 3**: Check browser console for errors +- Open DevTools → Console +- Look for Firebase/Firestore errors +- Common error: "Missing or insufficient permissions" + +#### Issue: Mission saved but not appearing in real-time + +**Check**: Verify onSnapshot listener is active +```javascript +// In browser console, you should see: +"📋 Reusing existing learner ID: your-id" +``` + +If not, the component may not be mounted or there's a React error. + +#### Issue: "Failed to save to learner history" in console + +This is a **non-critical warning**. The mission is still submitted to the operator queue. + +**Possible causes**: +1. Firestore rules preventing write access +2. Firebase not initialized +3. Network issue + +**Check Firestore Rules**: +```javascript +// Required rules for learner missions +match /learners/{learnerId}/missions/{missionId} { + // Allow anonymous writes (learners submitting missions) + allow write: if true; + + // Allow reads for anyone (could be more restrictive) + allow read: if true; +} +``` + +### Testing the Full Flow + +#### Test 1: Submission +```bash +# Expected console output: +✅ Mission submitted: {id: "abc123", ...} +✅ Mission saved to learner history +``` + +#### Test 2: Real-time Updates +1. Open `/history` page +2. Keep it open +3. Open Firebase Console in another tab +4. Update mission status: `submitted` → `in-progress` +5. History page should update **instantly** without refresh + +#### Test 3: Operator Completion +```javascript +// In Firebase Console, add to mission document: +{ + status: "completed", + completedAt: "2026-04-24T10:05:00Z", + youtubeVideoLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + executionNotes: "Test completion" +} +``` + +History page should show: +- ✅ Green "completed" badge +- ✅ YouTube video embed +- ✅ Operator notes section + +### Firestore Structure Reference + +``` +firestore/ +├─ missions/ (main collection for operators) +│ └─ {missionId}/ +│ ├─ id +│ ├─ sessionId (= learnerId) +│ ├─ code +│ ├─ status: "queued"|"processing"|"completed"|"failed" +│ ├─ yardId +│ └─ timestamps +│ +└─ learners/ (learner-specific data) + └─ {learnerId}/ + └─ missions/ (subcollection for history) + └─ {missionId}/ + ├─ missionId + ├─ status: "submitted"|"in-progress"|"completed" + ├─ submittedAt + ├─ completedAt + ├─ youtubeVideoLink + ├─ executionNotes + └─ code +``` + +### Migration: Existing Missions + +If you have existing missions in the main collection that aren't showing in history: + +```typescript +// Run this once in browser console on /history page +async function migrateExistingMissions() { + const learnerId = localStorage.getItem('mars-rover-learner-id'); + const sessionId = localStorage.getItem('rover-session-id'); + + // Fetch missions from main collection + const response = await fetch(`/api/missions?sessionId=${sessionId}`); + const data = await response.json(); + + // Import services + const { saveLearnerMission } = await import('/src/lib/services/learnerMissionService.ts'); + + // Save each to learner subcollection + for (const mission of data.missions) { + await saveLearnerMission(learnerId, { + missionId: mission.id, + missionName: `Mission ${mission.id.slice(0, 8)}`, + status: mission.status === 'queued' ? 'submitted' : + mission.status === 'processing' ? 'in-progress' : 'completed', + submittedAt: mission.submittedAt, + completedAt: mission.completedAt, + code: mission.code, + yardId: mission.yardId, + }); + } + + console.log('Migration complete! Refresh the page.'); +} + +migrateExistingMissions(); +``` + +### Debug Checklist + +- [ ] Firebase config in `.env.local` is correct +- [ ] `mars-rover-learner-id` exists in localStorage +- [ ] Browser console shows no React errors +- [ ] Firestore rules allow read/write to learners collection +- [ ] Mission appears in Firebase Console under both collections +- [ ] History page shows loading spinner then missions +- [ ] Real-time updates work (test by editing in Firebase Console) + +### Still Not Working? + +1. **Clear localStorage and try again**: +```javascript +localStorage.clear(); +location.reload(); +``` + +2. **Check Firebase connection**: +```javascript +import { getFirestoreClient } from '@/lib/firebase'; +const db = getFirestoreClient(); +console.log('Firebase initialized:', !!db); +``` + +3. **Verify onSnapshot is working**: +Add this temporarily to `MissionHistoryScaffold.tsx`: +```typescript +console.log('Subscribing to missions for learnerId:', id); +``` + +4. **Check Network tab**: +- DevTools → Network +- Look for Firestore requests +- Check for 403 (permissions) or 404 (not found) errors + +### Contact / Report Issues + +If none of these steps work, provide: +1. Browser console logs (full output) +2. Firestore structure screenshot +3. localStorage values +4. Firebase Console rules screenshot + +This will help diagnose the issue quickly. diff --git a/mission-authoring/docs/architecture/CLOUD_PLATFORM_REPO_CONTEXT.md b/mission-authoring/docs/architecture/CLOUD_PLATFORM_REPO_CONTEXT.md new file mode 100644 index 0000000..806d942 --- /dev/null +++ b/mission-authoring/docs/architecture/CLOUD_PLATFORM_REPO_CONTEXT.md @@ -0,0 +1,838 @@ +# 4tronix Rover Simulator Repo Context for Cloud Platform Planning + +## Purpose + +This document is a current repo briefing for an external planning/design assistant such as Claude. It explains what exists today, what each part of the repo does, what is reusable for a cloud platform, where the technical risks are, and how the current code relates to the planned stack: + +- Frontend/backend direction: TypeScript +- Auth/data/platform direction: Firebase + Google Cloud + +Read this together with: + +- `CLOUD_PLATFORM_QUESTIONS.md` for open product and architecture decisions +- `/Users/hlali/Documents/INF4027W/azure_devops_import_new.csv` for backlog and MVP intent + +## Executive Summary + +This repository currently contains three overlapping generations of the rover platform: + +1. A desktop simulator path + - PyQt6 simulator UI with an HTTP control surface + - Useful for local development and API compatibility + +2. A direct-control web path + - Flask pages that directly send commands to either the simulator or the real rover + - Useful as a prototype and operator UI reference + +3. A Yard classroom path + - Queue-based rover service + - Separate satellite web server + - Blockly + Python submission + - Monitor screen with camera + queue state + - SSE status streaming + - Mock-vs-real driver abstraction + - This is the strongest foundation for a future cloud platform + +If the goal is a cloud platform, the best source architecture is `yard/`, not the older direct-control Flask UI. + +## What the repo already proves is possible + +At a capability level, this repo already demonstrates that the platform can: + +- Preserve a rover-like Python programming API for both simulation and real hardware +- Accept high-level rover commands over HTTP +- Accept lower-level hardware-shaped payloads for compatibility +- Run a local desktop simulation of rover motion and steering +- Drive the real rover over HTTP on a local network +- Expose a browser UI for manual control and simple scripted sequences +- Stream camera frames over WebSocket +- Overlay AI detections on camera frames when using the Pi AI camera path +- Queue rover instructions and execute them sequentially +- Provide emergency stop / queue clear behavior +- Push live queue status to browsers via SSE +- Let learners submit Blockly-generated Python or raw Python +- Show a shared monitor view with live queue state and camera feed +- Run in mock mode without rover hardware for development/testing + +That means the repo already contains enough behavior to design: + +- A cloud mission queue +- A cloud operator console +- A browser coding interface +- A simulator-backed preflight workflow +- A rover-agent bridge from hardware to cloud +- A live mission monitoring experience + +## Repo structure + +### Root + +- `README.md` + - Main repo introduction and simulator usage + +- `roversimui.py` + - PyQt6 desktop simulator + - Runs an embedded Flask server on port `8524` + - Accepts rover commands over HTTP + - Models rover pose, heading, and wheel steering + - Imports `RTCWindow` support and can participate in streaming workflows + +- `roversimulator.py` + - Thin compatibility wrapper around `RoverWebDriver` + - Points to the simulator HTTP endpoint at `http://127.0.0.1:8524/` + - Lets user code do `import roversimulator as rover` + +- `rover_web_driver.py` + - Main HTTP driver abstraction + - Exposes a rover-style Python API + - Converts Python method calls into HTTP POST requests + - Reusable boundary for simulator or real rover control + +- `pi_camera_stream.py` + - Root-level camera streaming implementation + - WebSocket-based JPEG streaming + - AI detection overlay support + +- `connect_to_real_rover.py` + - Small example of using `RoverWebDriver` against a real rover host + +- Example scripts + - `square.py` + - `move-rover.py` + - `driveRover.py` + - `very-simple-example.py` + +- Tests + - `tests/test_rtc_implementation.py` + - `tests/simple_test.py` + +### `real-rover/` + +This is the real hardware path for the 4tronix rover. + +- `real-rover/rover.py` + - Hardware control library for the rover + - Direct GPIO / servo / sensor / LED behavior + +- `real-rover/rover_server.py` + - Flask HTTP server for real rover control on port `8523` + - Accepts both: + - high-level command payloads such as `{"command":"forward","speed":60}` + - low-level payloads containing `wheelMotors`, `servos`, `rgbLeds` + - Also drives LED patterns and steering servo coordination + +- `real-rover/pi_camera_stream.py` + - Older or alternative camera streaming implementation + +- `real-rover/requirements_pi.txt` + - Pi-specific dependencies including `picamera2`, `opencv-python`, `websockets`, Flask, and requests + +### `web_interface/` + +This is the older browser control path. + +- `web_interface/web_interface.py` + - Flask server with direct control routes for the real rover and simulator + - Supports per-command and timed sequence execution + +- `web_interface/templates/` + - `real.html` + - `sim.html` + - `index.html` + - `rtc_index.html` + +- `web_interface/rtc_client.py` + - Browser/client-side streaming support helper + +- `web_interface/run_simulator_with_streaming.py` + - Convenience launcher for simulator + streaming + +This layer is best treated as a prototype/reference UI, not the base of the cloud platform. + +### `yard/` + +This is the most cloud-relevant subsystem. + +- `yard/rover/` + - Queue-based rover control service + - Cleanest backend architecture in the repo + +- `yard/satellite/` + - Separate web server for the learner tablet UI and the shared monitor + - Proxies API calls to the rover server + - Hosts the camera stream in the current physical deployment model + +- `yard/docs/` + - Strong existing architecture and API documentation + +## Current runtime architectures + +## 1. Desktop simulator architecture + +### Main files + +- `roversimui.py` +- `roversimulator.py` +- `rover_web_driver.py` + +### How it works + +1. `roversimui.py` starts a PyQt6 window. +2. It starts an embedded Flask HTTP server on port `8524`. +3. `roversimulator.py` binds the rover-like Python API to `http://127.0.0.1:8524/`. +4. User code calls rover methods through the wrapper. +5. The simulator updates its motion state and redraws the rover. + +### What the simulator models + +- Vehicle `x/y` position +- Heading +- Servo angles +- Forward/reverse/spin behavior +- Steering arc behavior using approximate turning-circle math + +### What it does not model well yet + +- Obstacles or terrain +- Sensor realism +- Multi-user isolation +- Session persistence +- Cloud-grade lifecycle management +- A browser-native simulator + +### Why it matters for the cloud platform + +The simulator already preserves the rover API. That is valuable because the future browser simulator can target the same conceptual command set even if the implementation moves from Python/PyQt to TypeScript/Canvas/WebGL. + +## 2. Real rover local network architecture + +### Main files + +- `real-rover/rover_server.py` +- `real-rover/rover.py` +- `rover_web_driver.py` + +### How it works + +1. The rover Pi runs `real-rover/rover_server.py` on port `8523`. +2. Clients send JSON commands over HTTP. +3. The server translates the payload into direct rover hardware calls. +4. It also manages LED patterns and steering servo behavior. + +### Supported command styles + +#### High-level commands + +Examples: + +```json +{"command":"forward","speed":60} +{"command":"spinLeft","speed":50} +{"command":"steerRight","degrees":20,"seconds":1,"speed":60} +``` + +#### Low-level compatibility payload + +Example: + +```json +{ + "wheelMotors": { + "l": [100, 0], + "r": [100, 0] + }, + "servos": { + "9": 0, + "11": 0, + "13": 0, + "15": 0 + }, + "rgbLeds": { + "0": [255, 255, 255] + } +} +``` + +### Why it matters for the cloud platform + +This is the simplest and most proven rover execution endpoint. Even if the long-term cloud system uses a persistent bridge instead of raw HTTP, this file defines the hardware-level command vocabulary that must still be supported somewhere. + +## 3. Yard classroom architecture + +### Main files + +- `yard/rover/rover_server.py` +- `yard/rover/service.py` +- `yard/rover/drivers.py` +- `yard/satellite/web_server.py` +- `yard/satellite/camera_server.py` +- `yard/satellite/templates/code.html` +- `yard/satellite/templates/monitor.html` +- `yard/docs/architecture.md` +- `yard/docs/api.md` + +### Current physical model + +The docs describe a three-device setup: + +- Tablet/browser for learners +- Satellite device serving web UI and camera stream +- Rover device executing missions + +### Why this matters + +This is already structurally close to a cloud platform: + +- The learner UI is separated from rover execution +- The rover has a queue service instead of direct control only +- There is a monitor/operator surface +- There is an evented status stream +- There is an adapter boundary between app logic and hardware + +## Yard rover backend details + +### `yard/rover/service.py` + +This is the core application service. + +It currently provides: + +- A thread-safe in-memory instruction queue using `deque` +- History tracking +- Current-instruction state +- Background queue processor thread +- Emergency stop support via `_stop_requested` +- SSE subscriber fan-out +- Support for both movement commands and `run_python` + +### Supported queue commands + +The queue service executes: + +- `forward` +- `backward` / `reverse` +- `spin_left` +- `spin_right` +- `steer_left` +- `steer_right` +- `stop` +- `wait` +- `run_python` + +### `run_python` behavior + +`run_python` is especially important because it shows the repo has already moved beyond pure block sequences. + +Today it: + +- Receives Python code as text +- Exposes `rover` and `time` to the executed script +- Overrides `time.sleep` with an interruptible wait +- Restricts builtins to a small safe subset +- Tries to import real `rover` on Pi +- Falls back to `roversimulator` when not on hardware + +This is a major proof point for cloud planning because it shows the current product direction is not only “queue some motion blocks”, but “submit executable learner programs”. + +### Current safety limitations in `run_python` + +The current sandbox is not cloud-grade. It is useful, but not sufficient for hostile or multi-tenant internet usage. + +Current gaps: + +- Uses `exec()` directly +- No process isolation +- No container or VM boundary +- No CPU quota or memory quota +- No static AST allowlist enforcement +- No persistent per-user tenancy boundary +- No strong import escape analysis beyond limited globals/builtins + +Implication: for the new platform, `run_python` is a product proof, not a production-ready security model. + +### `yard/rover/drivers.py` + +This file is one of the best reuse points in the repo. + +It provides: + +- `RoverDriver` abstract interface +- `MockRoverDriver` +- `RealRoverDriver` +- `create_driver()` environment detection + +Why it matters: + +- It already separates application logic from hardware specifics +- It already supports local development without hardware +- It maps closely to the future idea of a cloud backend plus rover-side execution agent + +The future cloud system should preserve this idea, even if implemented in TypeScript interfaces instead of Python abstract base classes. + +### `yard/rover/rover_server.py` + +This is a thin Flask adapter over `RoverQueueService`. + +Endpoints: + +- `POST /queue/add` +- `POST /queue/clear` +- `GET /queue/status` +- `GET /queue/events` +- `GET /health` + +This is a very good example of keeping transport thin and pushing business logic into a service layer. + +## Yard satellite details + +### `yard/satellite/web_server.py` + +This server: + +- Serves `/code/` for learner interaction +- Serves `/monitor/` for the big-screen display +- Proxies queue calls to the rover +- Proxies the rover SSE stream to browsers +- Exposes a health endpoint with rover connectivity state + +This is effectively a primitive API gateway + frontend server. + +### `yard/satellite/camera_server.py` + +This server: + +- Runs a WebSocket service on port `8890` +- Captures frames from the Pi AI camera +- Runs IMX500 object detection +- Draws detection overlays +- Broadcasts base64 JPEG frames to all connected clients + +This proves the repo already supports: + +- Shared live viewing +- AI-overlay video +- A clean split between mission control UI and video pipeline + +But it is not yet a scalable cloud video architecture. It is a single-node broadcaster. + +### `yard/satellite/templates/code.html` + +The current learner UI is more capable than a simple Blockly page. + +It includes: + +- A Blockly pane +- A Python editor pane +- Run / stop / clear actions +- Mock mode support +- PWA setup via manifest and service worker + +That means the future platform does not need to invent the product concept from scratch. The repo already demonstrates a dual-mode coding experience: + +- visual programming +- text programming + +### `yard/satellite/templates/monitor.html` + +The monitor page combines: + +- live camera view +- queue state +- rover status / connection details +- display logic for current, pending, and historical instructions + +This is the strongest current reference for a future operator console / mission control surface. + +## Older web interface details + +### `web_interface/web_interface.py` + +This older layer provides: + +- Direct command routes for real rover +- Direct command routes for simulator +- Timed sequence execution +- Browser pages for real/sim targets + +It is useful as: + +- a UI interaction reference +- a reminder of the simplest command API shape + +It is not the best source for the cloud architecture because it couples browser requests directly to live rover control instead of going through a queue or mission service. + +## Camera and streaming status + +The repo contains multiple streaming approaches or experiments: + +- WebSocket JPEG streaming in `pi_camera_stream.py` +- Pi AI camera streaming in `yard/satellite/camera_server.py` +- RTC/WebRTC-related code and tests: + - `rtc_window.py` + - `rtc_window.py` imports in the simulator + - `tests/test_rtc_implementation.py` + - `web_interface/rtc_client.py` + +Important nuance: + +- The camera and monitor path that is clearly operational today is WebSocket + JPEG/base64 +- There are RTC/WebRTC artifacts in the repo, but this does not yet look like a finished, production-ready WebRTC pipeline + +For planning, treat current video support as: + +- proven: local WebSocket frame streaming +- exploratory/incomplete: WebRTC path + +## Existing APIs and abstractions worth preserving + +### 1. Rover command vocabulary + +The repo already converges around these rover concepts: + +- move forward +- move reverse/backward +- spin left/right +- steer left/right +- stop +- wait + +Even if the cloud platform changes languages and frameworks, this logical API should survive. + +### 2. Driver abstraction + +The `RoverDriver` pattern is strong and should be re-created in the new platform as a boundary between: + +- mission execution logic +- actual rover hardware transport + +### 3. Queue-first execution + +The Yard model is correct for real hardware sharing: + +- accept missions +- enqueue +- execute in order +- provide history +- support emergency stop + +That is much closer to a cloud education platform than direct remote driving. + +### 4. Event streaming + +The SSE approach used for queue updates is simple and effective for: + +- queue state +- mission status +- operator dashboard refresh + +The cloud version could keep SSE for mission status while using another transport for rover-agent communication. + +## What is mature vs experimental + +### Mature / reusable ideas + +- Rover-style Python API +- HTTP-based command transport +- Queue-based rover execution +- Hardware abstraction via drivers +- Blockly + Python learner workflow +- Shared monitor concept +- Emergency stop concept +- SSE status streaming +- Mock-vs-real execution mode + +### Useful but not production-grade + +- `run_python` sandbox +- WebSocket JPEG video broadcasting +- Older direct-control web UI +- Simulator streaming path + +### Experimental / uncertain + +- RTC/WebRTC support +- Production safety model for internet-facing learner code execution +- Multi-tenant cloud concerns +- Persistent mission storage +- Authentication and role system +- Multi-rover orchestration + +## Testing status + +The Yard subsystem is the most testable part of the repo. + +Relevant indicators: + +- `yard/rover/test_service.py` +- `yard/rover/test_integration.py` +- Architecture docs mention 52 tests in the Yard path + +This matters because it suggests the Yard backend is not just a prototype; it was intentionally structured for testability. + +By contrast, the broader repo is more mixed, with some exploratory or hardware-bound code paths that are harder to treat as production-grade foundations. + +## Dependencies and runtime shape + +### Root dependencies + +The repo currently centers on Python with: + +- Flask +- PyQt6 +- requests +- OpenCV +- numpy +- websockets + +### Pi-specific dependencies + +The hardware/camera path additionally relies on: + +- `picamera2` +- Pi/Linux-only dependencies +- rover hardware libraries and GPIO stack + +Implication: + +The current repo is operationally Python-first and device-local. The cloud platform will be a technology shift, not a simple deployment lift. + +## What the Azure DevOps CSV tells us + +The file `/Users/hlali/Documents/INF4027W/azure_devops_import_new.csv` adds important product context. + +It describes epics around: + +- operator authentication +- browser editor + sandbox +- browser-based simulator +- cloud mission queue +- rover-side execution agent +- operator console +- recording/upload pipeline +- mission history + +It also includes acceptance criteria and tests for many of those areas. + +### Important mismatch to call out + +The CSV backlog assumes several implementation choices that do not fully match the current direction you stated: + +- CSV assumption: FastAPI backend, Redis queue +- Current direction from user: TypeScript + Firebase + Google Cloud + +That means the CSV is still highly useful for: + +- product scope +- workflows +- acceptance criteria +- MVP sequencing +- non-functional requirements + +But it should not be treated as the final implementation spec for backend technology. + +### Best way to interpret the CSV + +Treat it as a backlog of product intentions, not as an authoritative architecture choice. + +For example: + +- Firebase Auth can replace the CSV’s backend-auth assumptions +- Firestore / Firebase + GCP services can replace parts of the Redis/FastAPI assumption +- Cloud Run / Functions / Pub/Sub / Firestore can be mapped onto the mission queue architecture + +## Implications for the new TypeScript + Firebase + GCP platform + +Based on the current repo, the cleanest interpretation is: + +### What should be reused conceptually + +- Mission queue model from `yard/rover/service.py` +- Driver/adapter boundary from `yard/rover/drivers.py` +- Learner coding workflow from `yard/satellite/templates/code.html` +- Monitor/operator workflow from `yard/satellite/templates/monitor.html` +- Rover command vocabulary from `rover_web_driver.py` and rover servers +- Simulator parity goal from `roversimui.py` + +### What should be reimplemented in the new stack + +- Frontend in TypeScript + - likely React/Next.js or similar +- Auth and role management in Firebase Auth +- Mission and history data in Firebase/Firestore +- Cloud services on GCP +- Safer code validation/execution model +- Browser-native simulator +- Production-grade video/recording pipeline + +### What should likely run on the physical rover side + +The current repo suggests the future platform still needs a rover-local agent or gateway responsible for: + +- maintaining a trusted outbound connection to cloud +- receiving approved missions +- mapping mission commands to hardware calls +- enforcing local safety stop behavior +- reporting status back to cloud +- optionally recording or relaying video + +That rover-local role is already hinted at by the current split between: + +- queue service +- real rover driver +- camera server + +## Suggested mapping from current repo to cloud platform components + +### Learner app + +Source inspiration: + +- `yard/satellite/templates/code.html` + +Cloud version: + +- TypeScript web app +- Blockly and/or Monaco/CodeMirror editor +- simulator run +- mission submit +- mission history + +### Operator console + +Source inspiration: + +- `yard/satellite/templates/monitor.html` + +Cloud version: + +- authenticated operator UI +- queue management +- mission status +- estop +- video feed +- rover health + +### Mission queue service + +Source inspiration: + +- `yard/rover/service.py` +- `yard/rover/rover_server.py` + +Cloud version: + +- TypeScript service +- persistent mission state +- queue semantics +- mission transitions +- audit log + +### Rover execution agent + +Source inspiration: + +- `yard/rover/drivers.py` +- `real-rover/rover_server.py` + +Cloud version: + +- rover-side agent process on Pi +- secure cloud connection +- mission execution boundary +- local safety enforcement + +### Simulator + +Source inspiration: + +- `roversimui.py` +- `roversimulator.py` + +Cloud version: + +- browser-native 2D or 3D simulator in TypeScript +- same logical rover API +- potentially a command playback engine instead of arbitrary Python execution + +### Video pipeline + +Source inspiration: + +- `yard/satellite/camera_server.py` +- `pi_camera_stream.py` + +Cloud version: + +- lower-latency live stream path +- recording pipeline +- cloud storage and playback + +## Key limitations Claude should know before planning + +1. The current repo is not a cloud backend. + It is a set of local-network and device-local systems. + +2. The current Python execution model is not secure enough for internet-scale, untrusted code execution. + +3. The current queue is in-memory, not durable. + +4. The current video path is not designed for broad concurrent scale. + +5. The simulator is desktop-native, not browser-native. + +6. Authentication, tenancy, and long-term persistence are mostly absent from the current codebase. + +7. The repo proves product direction and execution semantics better than it proves final cloud infrastructure choices. + +## Strongest reusable assets in this repo + +If only a few parts are used as canonical references for the new platform, they should be: + +1. `yard/rover/service.py` + - best expression of queue and mission execution semantics + +2. `yard/rover/drivers.py` + - best expression of boundary between business logic and hardware + +3. `yard/satellite/templates/code.html` + - best evidence of the intended learner programming UX + +4. `yard/satellite/templates/monitor.html` + - best evidence of the intended mission control UX + +5. `rover_web_driver.py` + - best compact expression of the rover command API surface + +6. `real-rover/rover_server.py` + - best expression of how commands ultimately hit the rover hardware today + +## Bottom-line recommendation for planning + +When designing the new platform, Claude should treat this repo as: + +- a strong source of product workflows +- a strong source of rover command semantics +- a strong source of queue/execution concepts +- a moderate source of simulator math and monitor UX +- a weak source of final cloud infrastructure and security design + +The cloud platform should be designed as a new system in TypeScript on Firebase + Google Cloud, but one that deliberately preserves: + +- the rover command model +- the queue-based execution model +- the learner Blockly/Python workflow +- the operator/monitor workflow +- the distinction between cloud orchestration and rover-local execution + +## Recommended next planning outputs + +After reading this repo context, the next useful design documents would be: + +1. A target cloud architecture diagram +2. A canonical mission lifecycle specification +3. A canonical rover command schema +4. A simulator parity spec listing what must match the real rover +5. A rover-agent protocol spec between cloud and physical rover +6. A security model for learner code validation and execution +7. An MVP scope doc aligned to TypeScript + Firebase + GCP instead of FastAPI + Redis + diff --git a/mission-authoring/docs/architecture/SIMULATOR_CLOUD_RUN_APPROACH.md b/mission-authoring/docs/architecture/SIMULATOR_CLOUD_RUN_APPROACH.md new file mode 100644 index 0000000..702b153 --- /dev/null +++ b/mission-authoring/docs/architecture/SIMULATOR_CLOUD_RUN_APPROACH.md @@ -0,0 +1,510 @@ +# Rover Simulator - Cloud Run Approach + +## Decision: Use Python Simulator via Cloud Run API + +After attempting to port the complex physics from `roversimui.py` to TypeScript, we decided to **reuse the proven Python simulator** via a Cloud Run service instead. + +### Why This Approach is Better + +#### 1. **Proven Physics** +- `roversimui.py` has 4-wheel steerable kinematics with proper Ackermann steering +- Complex turning circle calculations (lines 132-229) +- Already tested and working with real hardware +- Porting was tedious and error-prone + +#### 2. **Optimal for Poor Devices/Networks** +- **Server-side simulation** = zero client computation +- **Tiny payloads**: ~2KB request, ~5KB response +- **Works on any device**: No JS physics engine needed +- **Battery friendly**: Client just renders, doesn't calculate + +#### 3. **Cloud Run Benefits** +- **Auto-scaling**: 0 to 1000s of concurrent users +- **Cold start**: ~2-3 seconds (acceptable) +- **Cost**: Free tier covers most usage, <$5/month for hundreds of students +- **No maintenance**: Managed service + +#### 4. **Development Speed** +- Reuse existing Python code with minimal changes +- No complex TypeScript port needed +- Headless API took ~300 lines vs ~1,000+ for full port + +## Architecture + +``` +┌─────────────────────┐ +│ Browser/Client │ +│ │ +│ 1. Student writes │ +│ rover code │ +│ │ +│ 2. Click "Run" │ +└──────────┬──────────┘ + │ + │ POST /api/simulate + │ { commands: [...] } + │ ~2KB payload + │ + ▼ +┌─────────────────────┐ +│ Cloud Run API │ +│ (Python Flask) │ +│ │ +│ - Parse commands │ +│ - Run simulator │ +│ - Return trajectory│ +└──────────┬──────────┘ + │ + │ Response + │ { trajectory: [...] } + │ ~5KB payload + │ + ▼ +┌─────────────────────┐ +│ Browser/Client │ +│ │ +│ 3. Render canvas │ +│ Simple drawing │ +│ No physics calc │ +└─────────────────────┘ +``` + +## Implementation + +### Simulator Service (`simulator-service/`) + +**Files Created:** +- `simulator_api.py` - Flask API service (300 lines) +- `Dockerfile` - Cloud Run container +- `requirements.txt` - Python dependencies +- `README.md` - API documentation + +**Key Components:** + +1. **HeadlessRover Class** + - Ported core physics from `roversimui.py` + - No PyQt/GUI dependencies + - Pure computation + +2. **API Endpoints** + - `POST /api/simulate` - Execute mission + - `GET /health` - Health check + +3. **Command Execution** + - Parses high-level commands + - Sets servo/motor configuration + - Simulates over duration with 10 FPS sampling + - Returns complete trajectory + +### Request Format + +```json +POST /api/simulate + +{ + "commands": [ + { + "command": "forward", + "speed": 60, + "duration": 2 + }, + { + "command": "spinRight", + "speed": 50, + "duration": 1.5 + }, + { + "command": "steerLeft", + "degrees": 20, + "speed": 60, + "duration": 2 + } + ] +} +``` + +### Response Format + +```json +{ + "success": true, + "trajectory": [ + { + "x": 0, + "y": 0, + "heading": 0, + "speedL": 0, + "speedR": 0, + "servos": { + "9": 0, + "15": 0, + "11": 0, + "13": 0 + } + }, + { + "x": 0.6, + "y": 0.8, + "heading": 0, + "speedL": 60, + "speedR": 60, + "servos": {...} + }, + ... + ], + "duration": 5.5, + "final_position": { + "x": 120, + "y": 45, + "heading": 90 + }, + "frame_count": 56 +} +``` + +## Frontend Integration (TODO) + +### TypeScript API Client + +```typescript +// src/infrastructure/simulator/simulator-client.ts + +interface SimulateRequest { + commands: RoverCommand[]; +} + +interface RoverCommand { + command: 'forward' | 'reverse' | 'spinLeft' | 'spinRight' | 'steerLeft' | 'steerRight' | 'stop'; + speed?: number; + duration?: number; + degrees?: number; +} + +interface TrajectoryPoint { + x: number; + y: number; + heading: number; + speedL: number; + speedR: number; + servos: Record; +} + +interface SimulateResponse { + success: boolean; + trajectory: TrajectoryPoint[]; + duration: number; + final_position: { + x: number; + y: number; + heading: number; + }; +} + +export async function simulateMission(commands: RoverCommand[]): Promise { + const apiUrl = process.env.NEXT_PUBLIC_SIMULATOR_API_URL || 'http://localhost:8080'; + + const response = await fetch(`${apiUrl}/api/simulate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ commands }), + }); + + if (!response.ok) { + throw new Error(`Simulator API error: ${response.status}`); + } + + return response.json(); +} +``` + +### Canvas Renderer + +```typescript +// src/components/mission/RoverCanvas.tsx + +export function RoverCanvas({ trajectory }: { trajectory: TrajectoryPoint[] }) { + const canvasRef = useRef(null); + const [frame, setFrame] = useState(0); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas || trajectory.length === 0) return; + + const ctx = canvas.getContext('2d')!; + const state = trajectory[frame]; + + // Clear and draw yard + ctx.clearRect(0, 0, 600, 600); + ctx.fillStyle = '#634200'; + ctx.fillRect(0, 0, 600, 600); + + // Draw trajectory trail + ctx.strokeStyle = 'rgba(59, 130, 246, 0.5)'; + ctx.beginPath(); + trajectory.slice(0, frame + 1).forEach((point, i) => { + const x = (point.x + 200) * 1.5; + const y = 600 - (point.y + 150) * 1.5; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + }); + ctx.stroke(); + + // Draw rover at current position + const x = (state.x + 200) * 1.5; + const y = 600 - (state.y + 150) * 1.5; + + ctx.save(); + ctx.translate(x, y); + ctx.rotate((state.heading * Math.PI) / 180); + ctx.fillStyle = '#D3D3D3'; + ctx.fillRect(-12, -14, 24, 28); + ctx.restore(); + }, [trajectory, frame]); + + return ( +
+ + setFrame(parseInt(e.target.value))} + /> +
+ ); +} +``` + +### Editor Integration + +```typescript +// src/components/mission/MissionWorkspace.tsx + +'use client'; + +export function MissionWorkspace() { + const [code, setCode] = useState(''); + const [trajectory, setTrajectory] = useState([]); + const [loading, setLoading] = useState(false); + + const handleRun = async () => { + setLoading(true); + try { + // Parse code to commands + const commands = parseRoverCode(code); + + // Call simulator API + const result = await simulateMission(commands); + + // Display trajectory + setTrajectory(result.trajectory); + } catch (error) { + console.error('Simulation failed:', error); + } finally { + setLoading(false); + } + }; + + return ( +
+ + +
+ ); +} +``` + +## Deployment + +### 1. Local Testing + +```bash +cd simulator-service + +# Create virtual environment +python3 -m venv venv +source venv/bin/activate + +# Install dependencies +pip install -r requirements.txt + +# Run locally +python simulator_api.py +# Server at http://localhost:8080 +``` + +### 2. Test API + +```bash +curl -X POST http://localhost:8080/api/simulate \ + -H "Content-Type: application/json" \ + -d '{ + "commands": [ + {"command": "forward", "speed": 60, "duration": 2}, + {"command": "spinRight", "speed": 50, "duration": 1} + ] + }' | jq +``` + +### 3. Deploy to Cloud Run + +```bash +# Set your GCP project +export PROJECT_ID=your-gcp-project-id + +# Build container +gcloud builds submit --tag gcr.io/$PROJECT_ID/rover-simulator + +# Deploy to Cloud Run +gcloud run deploy rover-simulator \ + --image gcr.io/$PROJECT_ID/rover-simulator \ + --platform managed \ + --region us-central1 \ + --allow-unauthenticated \ + --memory 512Mi \ + --cpu 1 \ + --timeout 60s \ + --max-instances 10 + +# Get service URL +gcloud run services describe rover-simulator \ + --region us-central1 \ + --format='value(status.url)' +``` + +### 4. Configure Frontend + +Add to `.env.local`: +``` +NEXT_PUBLIC_SIMULATOR_API_URL=https://rover-simulator-xxx.run.app +``` + +## Performance Characteristics + +### Latency +- **Cold start**: 2-3 seconds (first request after idle) +- **Warm instance**: 50-100ms +- **Typical mission**: 10 commands = ~100ms + +### Payload Sizes +- **Request**: ~2KB (10 commands) +- **Response**: ~5KB (50 trajectory points) +- **Total round trip**: ~7KB + +### Cost (Free Tier) +- **Requests**: 2 million/month free +- **Compute**: 360,000 vCPU-seconds free +- **Memory**: 180,000 GiB-seconds free +- **Typical usage**: <$5/month for 100s of students + +### Scalability +- **Concurrent users**: Unlimited (auto-scales) +- **Max instances**: Configurable (default: 10) +- **Each instance**: ~10-20 concurrent requests + +## Comparison: TypeScript Port vs Cloud Run + +| Aspect | TypeScript Port | Cloud Run API | +|--------|----------------|---------------| +| **Development Time** | ~4 hours | ~1 hour | +| **Code Complexity** | ~1,000 lines TS + tests | ~300 lines Python | +| **Maintenance** | Complex physics to maintain | Reuses proven code | +| **Client Performance** | Heavy JS computation | Zero computation | +| **Works on Poor Devices** | Struggles on old hardware | Works on anything | +| **Network Usage** | 500KB bundle one-time | 7KB per simulation | +| **Accuracy** | Bugs in port | 100% accurate (original) | +| **Cost** | Free (client-side) | ~$5/month (negligible) | + +**Winner:** Cloud Run API for this use case (poor devices/networks) + +## Future Enhancements + +### Phase 1 (MVP) +- ✅ Headless simulator API +- ✅ Basic command execution +- ✅ Trajectory output +- ⏳ Frontend canvas renderer +- ⏳ Code parser (Python-like → commands) + +### Phase 2 (Enhanced) +- ⏳ WebSocket streaming for live updates +- ⏳ Obstacles/terrain in yard +- ⏳ Sensor simulation (ultrasonic, etc.) +- ⏳ Collision detection + +### Phase 3 (Advanced) +- ⏳ Multiple yards/scenarios +- ⏳ Mission validation/scoring +- ⏳ Replay/share trajectories +- ⏳ 3D visualization option + +## Related Files + +### Created +- `/simulator-service/simulator_api.py` +- `/simulator-service/Dockerfile` +- `/simulator-service/requirements.txt` +- `/simulator-service/README.md` +- `/simulator-service/.dockerignore` + +### Reverted (from TypeScript port attempt) +- `src/infrastructure/simulator/rover-movement.ts` → back to scaffold +- `src/infrastructure/simulator/simulator-executor.ts` → back to scaffold +- `src/components/mission/RoverSimulatorScaffold.tsx` → back to scaffold +- `src/components/mission/MissionWorkspaceScaffold.tsx` → back to scaffold +- `src/components/mission/RoverEditorScaffold.tsx` → back to scaffold + +### TODO (Frontend Integration) +- `src/infrastructure/simulator/simulator-client.ts` - API client +- `src/components/mission/RoverCanvas.tsx` - Canvas renderer +- `src/components/mission/MissionWorkspace.tsx` - Editor integration +- `src/utils/code-parser.ts` - Python-like code → commands + +## Next Steps + +1. **Deploy Simulator Service** + ```bash + cd simulator-service + # Follow deployment steps above + ``` + +2. **Test API** + ```bash + curl -X POST https://your-url.run.app/api/simulate -d '{"commands":[...]}' + ``` + +3. **Implement Frontend** + - Create API client + - Build canvas renderer + - Connect editor to API + - Add loading states/error handling + +4. **User Testing** + - Test on poor devices + - Test on slow networks + - Validate physics accuracy + - Gather feedback + +## Conclusion + +The Cloud Run approach is **significantly better** than the TypeScript port for this use case: + +- ✅ **Faster to implement** (1 hour vs 4+ hours) +- ✅ **More reliable** (reuses proven code) +- ✅ **Better for poor devices** (zero client computation) +- ✅ **Better for poor networks** (7KB vs 500KB) +- ✅ **Easier to maintain** (one source of truth) +- ✅ **Scalable** (Cloud Run handles traffic) +- ✅ **Cost-effective** (<$5/month) + +The small latency cost (50-100ms) is **well worth** the benefits, especially for the target audience with poor devices and networks. + +--- + +**Implementation Date:** 2026-04-19 +**Approach:** Cloud Run Python API +**Status:** Service ready, frontend integration TODO +**Decision By:** User (after seeing TypeScript port complexity) diff --git a/mission-authoring/docs/features/ANONYMOUS_LEARNER_IMPLEMENTATION_SUMMARY.md b/mission-authoring/docs/features/ANONYMOUS_LEARNER_IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..3d09ef1 --- /dev/null +++ b/mission-authoring/docs/features/ANONYMOUS_LEARNER_IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,362 @@ +# Anonymous Learner System - Implementation Summary + +## Overview + +Successfully implemented a privacy-first, anonymous learner identification system for the Mars Rover Cloud Platform. Learners can now use the platform without email/password signup while maintaining persistent identity and mission tracking. + +## ✅ Implementation Complete + +### Core System Components + +| Component | File | Purpose | +|-----------|------|---------| +| **Learner Entity** | `src/core/domain/entities/Learner.ts` | Domain model for anonymous learners | +| **Repository Interface** | `src/core/domain/repositories/ILearnerRepository.ts` | Contract for learner data operations | +| **Firestore Repository** | `src/infrastructure/persistence/FirestoreLearnerRepository.ts` | Firestore implementation of learner repository | +| **Anonymous Auth Service** | `src/lib/anonymous-auth.ts` | Session ID generation and localStorage management | +| **Learner Context** | `src/contexts/LearnerContext.tsx` | React context for global learner state | + +### UI Components + +| Component | File | Purpose | +|-----------|------|---------| +| **Profile Card** | `src/components/learner/LearnerProfileCard.tsx` | Display learner identity and stats | +| **Dashboard** | `src/components/learner/LearnerDashboard.tsx` | Full learner dashboard with history | + +### API Routes + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/api/learner/profile` | GET | Retrieve learner profile by sessionId | +| `/api/learner/profile` | PATCH | Update learner display name | +| `/api/learner/stats` | GET | Get learner mission statistics | + +### Tests + +| Test File | Coverage | +|-----------|----------| +| `src/__tests__/unit/anonymous-auth.test.ts` | Session management, localStorage, fingerprinting | +| `src/__tests__/unit/Learner.test.ts` | Entity creation, validation, sanitization | + +### Documentation + +| Document | Purpose | +|----------|---------| +| `ANONYMOUS_LEARNER_SYSTEM.md` | Complete system documentation and API reference | +| `MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md` | Step-by-step integration guide | +| This file | Implementation summary | + +## Key Features Delivered + +### 1. Privacy-First Design ✅ +- ❌ No email required +- ❌ No password required +- ❌ No personal information collected +- ✅ Anonymous session IDs (nanoid with 149 bits entropy) +- ✅ Local storage only (no tracking cookies) + +### 2. Session Management ✅ +- ✅ Automatic session creation on first visit +- ✅ Persistent across page reloads via localStorage +- ✅ Browser fingerprinting for device identification +- ✅ Manual session reset capability +- ✅ Graceful handling of corrupted data + +### 3. Profile Management ✅ +- ✅ Optional display name (max 20 characters) +- ✅ PII sanitization (removes email patterns) +- ✅ Random avatar color generation +- ✅ Multi-device tracking +- ✅ Last active timestamp + +### 4. Mission Tracking ✅ +- ✅ Total mission counter +- ✅ Completed mission counter +- ✅ Success rate calculation +- ✅ Mission history by sessionId +- ✅ Firestore atomic increments + +### 5. Developer Experience ✅ +- ✅ Clean architecture (repository pattern) +- ✅ Type-safe TypeScript interfaces +- ✅ React hooks for easy integration +- ✅ Comprehensive unit tests +- ✅ Example components +- ✅ Migration guide + +## Architecture Highlights + +### Data Flow + +``` +Browser → localStorage (sessionId) + ↓ +LearnerContext (React) + ↓ +FirestoreLearnerRepository + ↓ +Firestore 'learners' collection +``` + +### Mission Submission Flow + +``` +1. User submits mission +2. Get sessionId from LearnerContext +3. Include sessionId in mission payload +4. Save mission to Firestore +5. Increment learner.missionCount +6. On completion → increment learner.completedMissions +``` + +### Session Lifecycle + +``` +First Visit: + - Generate nanoid (21 chars) + - Store in localStorage + - Create Firestore document + - Assign random avatar color + +Return Visit: + - Read sessionId from localStorage + - Fetch learner from Firestore + - Update lastActiveAt timestamp + +Session Reset: + - Clear localStorage + - Generate new sessionId + - Create new Firestore document +``` + +## Security Considerations + +### What We Collect +- ✅ Random session ID (non-identifiable) +- ✅ Optional display name (sanitized) +- ✅ Mission statistics (counts only) +- ✅ Basic device fingerprint (for analytics) +- ✅ Timestamps (activity tracking) + +### What We Don't Collect +- ❌ Email addresses +- ❌ Passwords +- ❌ Real names +- ❌ IP addresses (not stored) +- ❌ Precise location +- ❌ Tracking pixels +- ❌ Third-party analytics + +### Privacy Protections +- Display names sanitized to remove email patterns +- No cross-site tracking +- No external analytics on learner data +- Session data stored locally (localStorage) +- GDPR-friendly (no PII) + +## Integration Points + +### Already Integrated +✅ Root layout (`src/app/layout.tsx`) - LearnerProvider added +✅ Mission entity - sessionId field exists + +### Requires Integration +⚠️ Mission submission API - Add sessionId parameter +⚠️ Mission completion handler - Increment completion counter +⚠️ Mission history page - Filter by sessionId +⚠️ UI components - Add LearnerProfileCard to mission page + +See `MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md` for step-by-step instructions. + +## Testing + +### Unit Tests + +```bash +# Run learner tests +npm test Learner +npm test anonymous-auth + +# Run all tests +npm test +``` + +### Manual Testing + +1. **First Visit** + - Open browser (incognito recommended) + - Visit any page + - Check localStorage for `mars-rover-session-id` + - Verify Firestore document created in `learners` collection + +2. **Profile Update** + - Update display name + - Verify sanitization works (try entering email) + - Verify persistence after reload + +3. **Mission Submission** + - Submit a mission + - Verify `missionCount` increments + - Complete mission + - Verify `completedMissions` increments + +4. **Session Reset** + - Use `resetSession()` from context + - Verify new sessionId generated + - Verify new Firestore document created + +## Performance Metrics + +### Firestore Operations Per Action + +| Action | Reads | Writes | +|--------|-------|--------| +| Page load (existing user) | 1 | 1 (update timestamp) | +| Page load (new user) | 1 | 1 (create profile) | +| Submit mission | 0 | 1 (increment counter) | +| Complete mission | 0 | 1 (increment counter) | +| Update display name | 0 | 1 | + +### Storage Impact + +- **localStorage**: ~200 bytes per session +- **Firestore**: ~500 bytes per learner document +- **Scale**: Supports 1M+ learners with basic Firestore plan + +## Future Enhancements + +### Planned Features +- [ ] Achievement/badge system +- [ ] Leaderboard (optional opt-in) +- [ ] Session transfer via QR code +- [ ] Multi-device session sync +- [ ] Export session history + +### Progressive Enhancement +- [ ] Optional authentication (preserve sessionId) +- [ ] Social login (link anonymous session) +- [ ] Parent/teacher accounts (manage learner sessions) +- [ ] Class/group features + +## Dependencies + +### Required Packages (Already Installed) +- ✅ `nanoid` v5.1.9 - Cryptographically strong ID generation +- ✅ `firebase` v12.12.0 - Client SDK +- ✅ `firebase/firestore` - Database operations + +### No Additional Dependencies Required +All implementation uses existing project dependencies. + +## Firestore Schema + +### Collection: `learners` + +```typescript +{ + // Document ID: sessionId (nanoid) + + id: string; // Same as document ID + sessionId: string; // Unique session identifier + displayName?: string; // Optional nickname (max 20 chars) + avatarColor?: string; // Hex color code + missionCount: number; // Total missions submitted + completedMissions: number; // Successfully completed missions + createdAt: Timestamp; // Account creation + lastActiveAt: Timestamp; // Last activity + devices: [ + { + sessionId: string; + firstSeenAt: Timestamp; + lastSeenAt: Timestamp; + deviceFingerprint?: string; + } + ] +} +``` + +### Indexes (Optional for Performance) + +``` +learners + - lastActiveAt DESC + - missionCount DESC (for leaderboards) +``` + +## Monitoring & Analytics + +### Recommended Queries + +```typescript +// Active learners (last 7 days) +const activeCount = await learnerRepo.getActiveLearnerCount(7); + +// Top learners by missions +query( + collection(db, 'learners'), + orderBy('missionCount', 'desc'), + limit(10) +); + +// Success rate distribution +// (Query all learners, calculate client-side) +``` + +### Firebase Console Metrics +- Monitor `learners` collection size +- Track read/write operations +- Set budget alerts if needed + +## Support & Resources + +### Documentation +- Main docs: `ANONYMOUS_LEARNER_SYSTEM.md` +- Migration guide: `MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md` +- Code examples: `src/components/learner/LearnerDashboard.tsx` + +### Key Files to Reference +- Entity: `src/core/domain/entities/Learner.ts` +- Repository: `src/infrastructure/persistence/FirestoreLearnerRepository.ts` +- Context: `src/contexts/LearnerContext.tsx` +- Tests: `src/__tests__/unit/Learner.test.ts` + +## Success Criteria ✅ + +All requirements met: + +- ✅ **No email/password required** - Anonymous session-based auth +- ✅ **Persistent identity** - localStorage + Firestore +- ✅ **Privacy-first** - No PII collection +- ✅ **Mission tracking** - Counters and statistics +- ✅ **Type-safe** - Full TypeScript coverage +- ✅ **Tested** - Comprehensive unit tests +- ✅ **Documented** - Complete documentation +- ✅ **Production-ready** - Clean architecture, error handling + +## Next Steps + +1. **Run Tests** + ```bash + npm test + ``` + +2. **Review Documentation** + - Read `ANONYMOUS_LEARNER_SYSTEM.md` + - Follow `MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md` + +3. **Integrate with Mission System** + - Update mission submission to include sessionId + - Add learner profile UI to mission page + - Test end-to-end flow + +4. **Deploy** + - Update Firestore security rules + - Deploy to staging + - Test in production-like environment + - Monitor Firestore usage + +--- + +**Implementation Date**: 2026-04-24 +**Status**: ✅ Complete and Ready for Integration +**Next Owner**: Development team for integration testing diff --git a/mission-authoring/docs/features/ANONYMOUS_LEARNER_SYSTEM.md b/mission-authoring/docs/features/ANONYMOUS_LEARNER_SYSTEM.md new file mode 100644 index 0000000..81bff4e --- /dev/null +++ b/mission-authoring/docs/features/ANONYMOUS_LEARNER_SYSTEM.md @@ -0,0 +1,376 @@ +# Anonymous Learner Identification System + +## Overview + +This system provides privacy-first, anonymous learner identification without requiring email or password authentication. Learners are identified using browser-based session IDs generated with cryptographically strong random identifiers. + +## Key Features + +- ✅ **No Authentication Required**: Learners can start immediately without signup +- ✅ **Privacy-First**: No PII collection, session stored locally +- ✅ **Persistent Identity**: Session persists across page reloads via localStorage +- ✅ **Optional Personalization**: Learners can optionally add a display name +- ✅ **Mission Tracking**: Track mission submissions and completion rates +- ✅ **Multi-Device Detection**: Basic device fingerprinting for analytics + +## Architecture + +### Core Components + +1. **`Learner` Entity** (`src/core/domain/entities/Learner.ts`) + - Domain model for anonymous learners + - Tracks mission statistics and activity + - Supports multiple device sessions + +2. **Anonymous Auth Service** (`src/lib/anonymous-auth.ts`) + - Generates unique session IDs using `nanoid` (21 chars, ~149 bits entropy) + - Manages localStorage persistence + - Provides browser fingerprinting for device tracking + +3. **LearnerContext** (`src/contexts/LearnerContext.tsx`) + - React context for managing learner state + - Auto-initializes session on app mount + - Syncs with Firestore for persistence + +4. **Firestore Repository** (`src/infrastructure/persistence/FirestoreLearnerRepository.ts`) + - Implements `ILearnerRepository` interface + - Manages learner data in Firestore `learners` collection + - Provides analytics queries + +## Data Model + +### Firestore Collection: `learners` + +Document ID: `{sessionId}` (nanoid-generated) + +```typescript +{ + id: string; // Same as sessionId + sessionId: string; // Unique browser session ID + displayName?: string; // Optional nickname (max 20 chars) + avatarColor?: string; // Random color for UI (#HEX) + missionCount: number; // Total missions submitted + completedMissions: number; // Successfully completed missions + createdAt: Timestamp; // Account creation + lastActiveAt: Timestamp; // Last activity + devices: [ + { + sessionId: string; + firstSeenAt: Timestamp; + lastSeenAt: Timestamp; + deviceFingerprint?: string; + } + ] +} +``` + +## Usage Guide + +### Frontend Integration + +#### 1. Access Learner Session in Components + +```tsx +import { useLearner } from '@/contexts/LearnerContext'; + +export function MyComponent() { + const { learner, sessionId, loading } = useLearner(); + + if (loading) return
Loading...
; + if (!learner) return
No session
; + + return ( +
+

Session: {sessionId}

+

Missions: {learner.missionCount}

+
+ ); +} +``` + +#### 2. Update Display Name + +```tsx +const { updateDisplayName } = useLearner(); + +await updateDisplayName('RoverPilot123'); +``` + +#### 3. Include LearnerProfileCard + +```tsx +import { LearnerProfileCard } from '@/components/learner/LearnerProfileCard'; + + +``` + +### Backend Integration + +#### Increment Mission Counter When Submitting + +```typescript +import { FirestoreLearnerRepository } from '@/infrastructure/persistence/FirestoreLearnerRepository'; + +const learnerRepo = new FirestoreLearnerRepository(); + +// When mission is submitted +await learnerRepo.incrementMissionCount(sessionId); + +// When mission completes successfully +await learnerRepo.incrementCompletedMissions(sessionId); +``` + +#### Get Learner Statistics + +```typescript +const stats = await learnerRepo.getStatistics(sessionId); +// Returns: { totalMissions, completedMissions, successRate } +``` + +## API Endpoints + +### GET `/api/learner/profile?sessionId={id}` + +Retrieve learner profile by session ID. + +**Response:** +```json +{ + "learner": { + "id": "abc123...", + "sessionId": "abc123...", + "displayName": "RoverPilot123", + "avatarColor": "#3B82F6", + "missionCount": 15, + "completedMissions": 12, + "createdAt": "2026-04-24T10:00:00Z", + "lastActiveAt": "2026-04-24T14:30:00Z" + } +} +``` + +### PATCH `/api/learner/profile` + +Update learner profile (display name only). + +**Request Body:** +```json +{ + "sessionId": "abc123...", + "updates": { + "displayName": "NewName" + } +} +``` + +### GET `/api/learner/stats?sessionId={id}` + +Get learner mission statistics. + +**Response:** +```json +{ + "stats": { + "totalMissions": 15, + "completedMissions": 12, + "successRate": 80 + } +} +``` + +## Security & Privacy Considerations + +### What We Collect +- ✅ Session ID (random, non-identifiable) +- ✅ Optional display name (sanitized, no PII) +- ✅ Mission statistics (counts only) +- ✅ Browser fingerprint (basic device tracking) + +### What We DON'T Collect +- ❌ Email addresses +- ❌ Passwords +- ❌ Real names +- ❌ IP addresses (not stored) +- ❌ Precise location data + +### Privacy Features + +1. **Local Storage Only**: Session ID stored in browser localStorage +2. **No External Tracking**: No third-party analytics on learner sessions +3. **PII Sanitization**: Display names are sanitized to remove email patterns +4. **Voluntary Participation**: Learners can reset session anytime + +## Session Management + +### Session Lifecycle + +1. **First Visit** + - Generate unique sessionId (nanoid) + - Store in localStorage + - Create learner document in Firestore + - Generate random avatar color + +2. **Return Visit** + - Retrieve sessionId from localStorage + - Fetch existing learner from Firestore + - Update lastActiveAt timestamp + +3. **Session Reset** + - User can clear session via `resetSession()` + - Creates new identity with new sessionId + +### Session Persistence + +Sessions persist across: +- ✅ Page reloads +- ✅ Browser restarts +- ✅ Different tabs (same browser) + +Sessions are lost when: +- ❌ Browser cache/localStorage is cleared +- ❌ User explicitly resets session +- ❌ Using different browser/device + +## Analytics & Reporting + +### Available Metrics + +```typescript +// Active learners in last N days +const activeCount = await learnerRepo.getActiveLearnerCount(7); + +// Individual learner stats +const stats = await learnerRepo.getStatistics(sessionId); +``` + +### Firestore Queries + +```typescript +// Find learners by last active +const q = query( + collection(db, 'learners'), + where('lastActiveAt', '>=', cutoffDate), + orderBy('lastActiveAt', 'desc') +); +``` + +## Testing + +### Unit Tests + +```bash +npm test Learner +npm test anonymous-auth +npm test FirestoreLearnerRepository +``` + +### Manual Testing Checklist + +- [ ] New learner session is created on first visit +- [ ] Session persists after page reload +- [ ] Display name can be updated +- [ ] Mission counters increment correctly +- [ ] Profile card displays correctly +- [ ] Session can be reset +- [ ] Works with localStorage disabled (graceful degradation) + +## Future Enhancements + +### Planned Features +- [ ] Multi-device session linking +- [ ] Session transfer via QR code +- [ ] Achievement/badge system +- [ ] Leaderboard (optional opt-in) +- [ ] Session history export + +### Progressive Enhancement Path +- [ ] Optional authentication (preserve sessionId when upgrading) +- [ ] Social login integration (link anonymous session) +- [ ] Parent/teacher accounts (link learner sessions) + +## Integration with Mission System + +The `Mission` entity already includes `sessionId` field: + +```typescript +interface Mission { + sessionId: string; // Links to learner + learnerUid?: string; // Optional for future auth + // ... other fields +} +``` + +### Updating Mission Submission Flow + +```typescript +// 1. Get learner session +const { sessionId } = useLearner(); + +// 2. Submit mission with sessionId +await fetch('/api/missions', { + method: 'POST', + body: JSON.stringify({ + sessionId, + code: '...', + yardId: '...', + }), +}); + +// 3. Increment learner mission counter +await learnerRepo.incrementMissionCount(sessionId); + +// 4. On mission completion +await learnerRepo.incrementCompletedMissions(sessionId); +``` + +## Troubleshooting + +### Session Not Persisting +- Check localStorage is enabled in browser +- Verify Firestore rules allow read/write to `learners` collection +- Check browser console for errors + +### Display Name Not Updating +- Ensure display name is ≤20 characters +- Check for PII patterns (emails are blocked) +- Verify Firestore connection + +### Mission Counters Not Incrementing +- Verify `sessionId` is passed to mission submission +- Check Firestore transaction permissions +- Review server logs for errors + +## Firestore Security Rules + +```javascript +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + match /learners/{learnerId} { + // Allow read if sessionId matches + allow read: if request.auth == null || + request.auth.token.learnerId == learnerId; + + // Allow create for anonymous users + allow create: if request.auth == null && + request.resource.data.sessionId == learnerId; + + // Allow update own profile + allow update: if request.auth == null && + request.resource.data.sessionId == learnerId; + } + } +} +``` + +## Related Documentation + +- [Mission Entity](./src/core/domain/entities/Mission.ts) +- [Firebase Setup](./src/lib/firebase.ts) +- [Repository Pattern](./src/core/domain/repositories/ILearnerRepository.ts) + +## Support + +For questions or issues, see: +- GitHub Issues: [mars-rover-cloud-platform/issues](https://github.com/...) +- Team Slack: #mars-rover-dev diff --git a/mission-authoring/docs/features/LEARNER_ID_QUICK_REFERENCE.md b/mission-authoring/docs/features/LEARNER_ID_QUICK_REFERENCE.md new file mode 100644 index 0000000..940f34c --- /dev/null +++ b/mission-authoring/docs/features/LEARNER_ID_QUICK_REFERENCE.md @@ -0,0 +1,241 @@ +# Learner ID System - Quick Reference + +## 🚀 Quick Start (3 Steps) + +### Step 1: Get Learner ID +```typescript +import { getLearnerID } from '@/lib/getLearnerID'; + +const learnerId = getLearnerID(); // Automatically persists in localStorage +``` + +### Step 2: Initialize Firestore (on app startup) +```typescript +import { initializeLearner } from '@/lib/initializeLearner'; + +const learner = await initializeLearner(); // Auto-creates document if new +``` + +### Step 3: Track Missions +```typescript +import { incrementMissionsCompleted } from '@/lib/initializeLearner'; + +await incrementMissionsCompleted(learnerId); // Updates Firestore +``` + +--- + +## 📋 Core Functions + +| Function | Purpose | Returns | +|----------|---------|---------| +| `getLearnerID()` | Get or generate unique ID | `string` (21 chars) | +| `clearLearnerID()` | Clear stored ID (reset) | `void` | +| `hasLearnerID()` | Check if ID exists | `boolean` | +| `initializeLearner()` | Initialize Firestore record | `Promise` | +| `incrementMissionsCompleted()` | Increment mission counter | `Promise` | +| `updateProgress()` | Update progress (0-100) | `Promise` | + +--- + +## 🎯 Common Use Cases + +### Use Case 1: Mission Submission +```typescript +import { getLearnerID } from '@/lib/getLearnerID'; + +async function submitMission(code: string) { + const learnerId = getLearnerID(); + + await fetch('/api/missions', { + method: 'POST', + body: JSON.stringify({ learnerId, code }), + }); +} +``` + +### Use Case 2: React Component +```tsx +import { useLearnerID } from '@/hooks/useLearnerID'; + +export function MyComponent() { + const { learnerId, learnerData, loading } = useLearnerID(); + + if (loading) return
Loading...
; + + return
Missions: {learnerData?.missionsCompleted}
; +} +``` + +### Use Case 3: App Initialization +```tsx +// src/app/layout.tsx +import { initializeLearner } from '@/lib/initializeLearner'; + +useEffect(() => { + initializeLearner(); // Call on app startup +}, []); +``` + +--- + +## 🗄️ Firestore Structure + +### Path +``` +learners/{learnerId} +``` + +### Document Schema +```typescript +{ + learnerId: string; // Unique ID (nanoid) + createdAt: Timestamp; // Creation time + missionsCompleted: number; // Mission counter + progress: number; // 0-100 percentage + lastActiveAt: Timestamp; // Last activity +} +``` + +--- + +## 🔒 How It Works + +### First Visit (New Learner) +``` +1. getLearnerID() → Generates: "xT4pQ9kLmN2rV8wY3zB5e" +2. Stores in localStorage: 'mars-rover-learner-id' +3. initializeLearner() → Creates Firestore doc: learners/xT4pQ9... +``` + +### Return Visit (Existing Learner) +``` +1. getLearnerID() → Retrieves from localStorage: "xT4pQ9..." +2. initializeLearner() → Updates lastActiveAt timestamp +3. Returns existing data +``` + +### Cache Cleared (Generate New) +``` +1. localStorage empty +2. getLearnerID() → Generates NEW ID +3. initializeLearner() → Creates NEW Firestore doc +4. New learner session begins +``` + +--- + +## ✅ Testing Checklist + +- [ ] First visit generates ID and stores in localStorage +- [ ] Page reload reuses same ID +- [ ] Firestore document created at `learners/{id}` +- [ ] `missionsCompleted` increments correctly +- [ ] Clear localStorage → new ID generated +- [ ] Progress updates (0-100) work + +### Test Commands +```bash +npm test getLearnerID +npm test Learner +``` + +--- + +## 📁 File Structure + +``` +src/ +├── lib/ +│ ├── getLearnerID.ts ✅ Core ID utility +│ └── initializeLearner.ts ✅ Firestore operations +├── hooks/ +│ └── useLearnerID.ts ✅ React hook +├── components/ +│ ├── learner/ +│ │ ├── LearnerProfileCard.tsx ✅ UI component +│ │ └── LearnerDashboard.tsx ✅ Dashboard +│ └── examples/ +│ └── SimpleLearnerExample.tsx ✅ Complete example +├── __tests__/unit/ +│ ├── getLearnerID.test.ts ✅ Tests +│ └── Learner.test.ts ✅ Tests +└── contexts/ + └── LearnerContext.tsx ✅ React context +``` + +--- + +## 🔧 API Routes (Optional) + +```typescript +// GET /api/learner/profile?sessionId={id} +// PATCH /api/learner/profile +// GET /api/learner/stats?sessionId={id} +``` + +--- + +## 🚨 Common Issues + +### Issue: ID not persisting +**Solution**: Check localStorage is enabled + +### Issue: Firestore permission denied +**Solution**: Update security rules to allow learner collection writes + +### Issue: Multiple IDs generated +**Solution**: Don't call `clearLearnerID()` in production code + +--- + +## 📚 Documentation + +| Document | Purpose | +|----------|---------| +| `ANONYMOUS_LEARNER_SYSTEM.md` | Complete system documentation | +| `USAGE_GUIDE_LEARNER_ID.md` | Detailed usage guide with examples | +| `MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md` | Step-by-step integration guide | +| `LEARNER_ID_QUICK_REFERENCE.md` | This quick reference (you are here) | + +--- + +## 💡 Best Practices + +✅ **Do:** +- Call `getLearnerID()` on mission submission +- Initialize learner on app startup +- Increment counters after mission completion +- Test in incognito mode + +❌ **Don't:** +- Manually create IDs +- Store PII with learner data +- Call `clearLearnerID()` in production +- Assume localStorage is always available + +--- + +## 🎯 Success Metrics + +- ✅ No email/password required +- ✅ Automatic ID generation +- ✅ localStorage persistence +- ✅ Firestore auto-sync +- ✅ Privacy-first (no PII) +- ✅ Collision-resistant IDs (149 bits entropy) + +--- + +## 🔗 Quick Links + +- **Utility**: `src/lib/getLearnerID.ts` +- **Hook**: `src/hooks/useLearnerID.ts` +- **Example**: `src/components/examples/SimpleLearnerExample.tsx` +- **Tests**: `src/__tests__/unit/getLearnerID.test.ts` + +--- + +**System Status**: ✅ Fully Implemented & Ready to Use + +**Last Updated**: 2026-04-24 diff --git a/mission-authoring/docs/features/MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md b/mission-authoring/docs/features/MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md new file mode 100644 index 0000000..ffe3a5f --- /dev/null +++ b/mission-authoring/docs/features/MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md @@ -0,0 +1,349 @@ +# Migration Guide: Adding Anonymous Learner System + +This guide explains how to integrate the anonymous learner identification system into existing mission submission workflows. + +## Prerequisites + +1. ✅ All new files have been created (see file list below) +2. ✅ `LearnerProvider` added to root layout +3. ✅ Firestore `learners` collection configured +4. ✅ `nanoid` package already installed (v5.1.9) + +## Step 1: Update Mission Submission API + +### Before (Without Learner Tracking) + +```typescript +// src/app/api/missions/route.ts +export async function POST(request: NextRequest) { + const { code, yardId } = await request.json(); + + const mission = { + id: nanoid(), + code, + yardId, + status: 'queued', + submittedAt: new Date().toISOString(), + }; + + await missionRepo.create(mission); + + return NextResponse.json({ missionId: mission.id }); +} +``` + +### After (With Learner Tracking) + +```typescript +// src/app/api/missions/route.ts +import { FirestoreLearnerRepository } from '@/infrastructure/persistence/FirestoreLearnerRepository'; + +const learnerRepo = new FirestoreLearnerRepository(); + +export async function POST(request: NextRequest) { + const { code, yardId, sessionId } = await request.json(); // Add sessionId + + // Validate sessionId + if (!sessionId) { + return NextResponse.json( + { error: 'Session ID required' }, + { status: 400 } + ); + } + + const mission = { + id: nanoid(), + code, + yardId, + sessionId, // Add sessionId to mission + status: 'queued', + submittedAt: new Date().toISOString(), + }; + + await missionRepo.create(mission); + + // Increment learner mission counter + try { + await learnerRepo.incrementMissionCount(sessionId); + } catch (error) { + console.error('Failed to update learner stats:', error); + // Don't fail the mission submission if stats update fails + } + + return NextResponse.json({ missionId: mission.id }); +} +``` + +## Step 2: Update Mission Completion Handler + +Add learner statistics tracking when missions complete: + +```typescript +// When mission status changes to 'completed' +if (mission.status === 'completed' && mission.executionResult?.isSuccessful) { + try { + await learnerRepo.incrementCompletedMissions(mission.sessionId); + } catch (error) { + console.error('Failed to update completion stats:', error); + } +} +``` + +## Step 3: Update Frontend Mission Submission + +### Before + +```tsx +// components/mission/MissionWorkspaceScaffold.tsx +const handleSubmit = async () => { + const response = await fetch('/api/missions', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + code, + yardId, + }), + }); +}; +``` + +### After + +```tsx +import { useLearner } from '@/contexts/LearnerContext'; + +export function MissionWorkspaceScaffold() { + const { sessionId } = useLearner(); // Add this + + const handleSubmit = async () => { + if (!sessionId) { + alert('Session not initialized. Please refresh the page.'); + return; + } + + const response = await fetch('/api/missions', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + code, + yardId, + sessionId, // Add sessionId + }), + }); + }; +} +``` + +## Step 4: Add Learner Profile UI + +Add the learner profile card to your mission page: + +```tsx +// src/app/mission/page.tsx +import { LearnerProfileCard } from '@/components/learner/LearnerProfileCard'; + +export default function MissionPage() { + return ( +
+ {/* Add profile card in sidebar or header */} + + + {/* Existing mission editor */} + +
+ ); +} +``` + +## Step 5: Update Mission History Queries + +Filter missions by sessionId: + +```typescript +// src/app/history/page.tsx +import { useLearner } from '@/contexts/LearnerContext'; + +export default function HistoryPage() { + const { sessionId } = useLearner(); + + useEffect(() => { + if (sessionId) { + fetchMissions(sessionId); + } + }, [sessionId]); + + async function fetchMissions(sessionId: string) { + const response = await fetch(`/api/missions?sessionId=${sessionId}`); + // ... + } +} +``` + +## Step 6: Update Firestore Security Rules + +Add rules for the `learners` collection: + +```javascript +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + // Anonymous learners collection + match /learners/{learnerId} { + // Anyone can read learner profiles (privacy-safe data only) + allow read: if true; + + // Anyone can create new learner profiles + allow create: if request.resource.data.sessionId == learnerId; + + // Only allow updating certain fields + allow update: if request.resource.data.sessionId == learnerId && + // Prevent tampering with mission counters directly + request.resource.data.missionCount >= resource.data.missionCount && + request.resource.data.completedMissions >= resource.data.completedMissions; + } + + // Missions collection - filter by sessionId + match /missions/{missionId} { + allow read: if request.auth == null; // Allow anonymous reads + allow create: if request.resource.data.sessionId != null; + } + } +} +``` + +## Step 7: Testing Checklist + +- [ ] **New User Flow** + - Visit site for first time + - Verify session is created in localStorage + - Verify learner document created in Firestore + - Verify avatar color assigned + +- [ ] **Mission Submission** + - Submit a mission + - Verify `sessionId` included in mission document + - Verify `missionCount` incremented in learner document + +- [ ] **Mission Completion** + - Complete a mission successfully + - Verify `completedMissions` incremented + - Verify success rate updates correctly + +- [ ] **Profile Updates** + - Update display name + - Verify sanitization (email removal) + - Verify persistence across reload + +- [ ] **Session Persistence** + - Reload page + - Verify session persists + - Verify learner data loads correctly + +- [ ] **Multi-Tab Behavior** + - Open multiple tabs + - Verify same session used + - Verify mission counters consistent + +## Common Issues & Solutions + +### Issue: Session not persisting + +**Cause**: localStorage disabled or cleared + +**Solution**: Add fallback message to users: + +```tsx +import { isStorageAvailable } from '@/lib/anonymous-auth'; + +if (!isStorageAvailable()) { + return
Please enable cookies to use this feature.
; +} +``` + +### Issue: Mission counter not incrementing + +**Cause**: Firestore permission denied + +**Solution**: Check security rules allow writes to `learners` collection + +### Issue: Display name contains email + +**Cause**: Sanitization not applied + +**Solution**: Always use `sanitizeDisplayName()` before saving: + +```typescript +import { sanitizeDisplayName } from '@/core/domain/entities/Learner'; + +const cleaned = sanitizeDisplayName(userInput); +``` + +## Rollback Plan + +If issues arise, you can disable learner tracking without breaking missions: + +1. Make `sessionId` optional in mission submission +2. Skip learner counter updates (add try-catch) +3. Hide learner profile UI components +4. Missions will still work without learner tracking + +```typescript +// Graceful degradation +const { sessionId } = useLearner(); + +await fetch('/api/missions', { + body: JSON.stringify({ + code, + yardId, + ...(sessionId && { sessionId }), // Optional + }), +}); +``` + +## Performance Considerations + +### Firestore Operations + +- **Mission submission**: +1 read, +1 write to learners (minimal overhead) +- **Mission completion**: +1 write to learners +- **Profile load**: 1 read on page load (cached in React context) + +### Optimization Tips + +1. **Batch updates**: Use Firestore batched writes for mission + learner updates +2. **Client-side caching**: LearnerContext caches profile data +3. **Async increments**: Use `increment()` to avoid read-modify-write cycles + +## Next Steps + +After migration is complete: + +1. Monitor Firestore usage in Firebase Console +2. Review learner statistics for insights +3. Consider adding achievement system +4. Plan optional authentication upgrade path + +## Files Created + +- ✅ `src/core/domain/entities/Learner.ts` +- ✅ `src/core/domain/repositories/ILearnerRepository.ts` +- ✅ `src/lib/anonymous-auth.ts` +- ✅ `src/contexts/LearnerContext.tsx` +- ✅ `src/infrastructure/persistence/FirestoreLearnerRepository.ts` +- ✅ `src/components/learner/LearnerProfileCard.tsx` +- ✅ `src/components/learner/LearnerDashboard.tsx` +- ✅ `src/app/api/learner/profile/route.ts` +- ✅ `src/app/api/learner/stats/route.ts` +- ✅ `src/__tests__/unit/anonymous-auth.test.ts` +- ✅ `src/__tests__/unit/Learner.test.ts` +- ✅ `ANONYMOUS_LEARNER_SYSTEM.md` +- ✅ `MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md` + +## Support + +For questions during migration: +- Review `ANONYMOUS_LEARNER_SYSTEM.md` for detailed API docs +- Check unit tests for usage examples +- Test in development environment first diff --git a/mission-authoring/docs/features/USAGE_GUIDE_LEARNER_ID.md b/mission-authoring/docs/features/USAGE_GUIDE_LEARNER_ID.md new file mode 100644 index 0000000..8a30ec4 --- /dev/null +++ b/mission-authoring/docs/features/USAGE_GUIDE_LEARNER_ID.md @@ -0,0 +1,514 @@ +# Usage Guide: getLearnerID() Utility + +## Quick Start + +The `getLearnerID()` utility provides automatic learner identification without requiring email or password. + +### Basic Usage + +```typescript +import { getLearnerID } from '@/lib/getLearnerID'; + +// Get or generate learner ID (automatically persists in localStorage) +const learnerId = getLearnerID(); +console.log('Learner ID:', learnerId); +``` + +### React Hook + +```tsx +import { useLearnerID } from '@/hooks/useLearnerID'; + +export function MyComponent() { + const { learnerId, learnerData, loading } = useLearnerID(); + + if (loading) return
Loading...
; + + return ( +
+

Your ID: {learnerId}

+

Missions Completed: {learnerData?.missionsCompleted}

+

Progress: {learnerData?.progress}%

+
+ ); +} +``` + +## How It Works + +### 1. First Visit (New Learner) + +```typescript +// User visits site for first time +const learnerId = getLearnerID(); +// → Generates: "xT4pQ9kLmN2rV8wY3zB5e" (21 chars, collision-resistant) +// → Stores in localStorage: 'mars-rover-learner-id' +// → Creates Firestore document: learners/xT4pQ9kLmN2rV8wY3zB5e +``` + +**Firestore Document Created:** +```json +{ + "learnerId": "xT4pQ9kLmN2rV8wY3zB5e", + "createdAt": "2026-04-24T14:30:00Z", + "missionsCompleted": 0, + "progress": 0, + "lastActiveAt": "2026-04-24T14:30:00Z" +} +``` + +### 2. Return Visit (Existing Learner) + +```typescript +// User returns to site +const learnerId = getLearnerID(); +// → Retrieves from localStorage: "xT4pQ9kLmN2rV8wY3zB5e" +// → Updates Firestore: lastActiveAt timestamp +// → Returns existing ID (no new ID generated) +``` + +### 3. Cache Cleared (Generate New ID) + +```typescript +// User clears browser cache/localStorage +const learnerId = getLearnerID(); +// → localStorage empty, generates NEW ID +// → Creates NEW Firestore document +// → New learner session begins +``` + +## API Reference + +### `getLearnerID(): string` + +Gets or creates a unique learner ID. + +**Returns:** Learner ID (21 character string) + +**Behavior:** +- ✅ Checks localStorage first +- ✅ Reuses existing ID if found +- ✅ Generates new ID if not found +- ✅ Stores in localStorage automatically + +**Example:** +```typescript +const id = getLearnerID(); +// "xT4pQ9kLmN2rV8wY3zB5e" +``` + +### `clearLearnerID(): void` + +Clears the stored learner ID (useful for testing or reset). + +**Example:** +```typescript +clearLearnerID(); +// localStorage cleared +// Next call to getLearnerID() generates new ID +``` + +### `hasLearnerID(): boolean` + +Checks if a learner ID exists in storage. + +**Returns:** `true` if ID exists, `false` otherwise + +**Example:** +```typescript +if (hasLearnerID()) { + console.log('Returning user'); +} else { + console.log('New user'); +} +``` + +### `initializeLearner(): Promise` + +Initializes learner in Firestore (auto-creates document if needed). + +**Returns:** Promise resolving to `LearnerRecord` + +**Example:** +```typescript +import { initializeLearner } from '@/lib/initializeLearner'; + +const learner = await initializeLearner(); +console.log('Missions:', learner.missionsCompleted); +``` + +### `incrementMissionsCompleted(learnerId: string): Promise` + +Increments the learner's completed missions count. + +**Example:** +```typescript +import { incrementMissionsCompleted } from '@/lib/initializeLearner'; + +await incrementMissionsCompleted(learnerId); +``` + +### `updateProgress(learnerId: string, progress: number): Promise` + +Updates learner's progress (0-100). + +**Example:** +```typescript +import { updateProgress } from '@/lib/initializeLearner'; + +await updateProgress(learnerId, 75); // 75% complete +``` + +## Integration Examples + +### Mission Submission + +```typescript +import { getLearnerID } from '@/lib/getLearnerID'; +import { incrementMissionsCompleted } from '@/lib/initializeLearner'; + +// When submitting a mission +async function submitMission(code: string) { + const learnerId = getLearnerID(); + + const response = await fetch('/api/missions', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + learnerId, + code, + yardId: 'uct-rover-1', + }), + }); + + const { missionId } = await response.json(); + return missionId; +} + +// When mission completes +async function onMissionComplete(learnerId: string) { + await incrementMissionsCompleted(learnerId); +} +``` + +### Progress Tracking + +```typescript +import { getLearnerID } from '@/lib/getLearnerID'; +import { updateProgress } from '@/lib/initializeLearner'; + +// Update progress when learner completes a challenge +async function completeChallenge(challengeNumber: number, totalChallenges: number) { + const learnerId = getLearnerID(); + const progress = Math.round((challengeNumber / totalChallenges) * 100); + + await updateProgress(learnerId, progress); +} +``` + +### React Component + +```tsx +'use client'; + +import { useEffect, useState } from 'react'; +import { getLearnerID } from '@/lib/getLearnerID'; +import { initializeLearner, LearnerRecord } from '@/lib/initializeLearner'; + +export function LearnerProfile() { + const [learner, setLearner] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function init() { + const record = await initializeLearner(); + setLearner(record); + setLoading(false); + } + init(); + }, []); + + if (loading) return
Loading...
; + if (!learner) return
Error loading profile
; + + return ( +
+

Your Progress

+

ID: {learner.learnerId}

+

Missions Completed: {learner.missionsCompleted}

+

Progress: {learner.progress}%

+

Member Since: {new Date(learner.createdAt).toLocaleDateString()}

+
+ ); +} +``` + +### API Route (Mission Submission) + +```typescript +// src/app/api/missions/route.ts +import { NextRequest, NextResponse } from 'next/server'; + +export async function POST(request: NextRequest) { + const { learnerId, code, yardId } = await request.json(); + + // Validate learner ID + if (!learnerId || learnerId.length !== 21) { + return NextResponse.json( + { error: 'Valid learner ID required' }, + { status: 400 } + ); + } + + // Create mission with learner ID + const mission = { + id: nanoid(), + learnerId, // Associate with learner + code, + yardId, + status: 'queued', + submittedAt: new Date().toISOString(), + }; + + // Save to Firestore + await missionRepo.create(mission); + + return NextResponse.json({ missionId: mission.id }); +} +``` + +## Firestore Structure + +### Collection: `learners/{learnerId}` + +```typescript +interface LearnerDocument { + learnerId: string; // Unique ID (nanoid) + createdAt: Timestamp; // When learner was created + missionsCompleted: number; // Total missions completed + progress: number; // Progress percentage (0-100) + lastActiveAt: Timestamp; // Last activity timestamp +} +``` + +### Firestore Queries + +```typescript +import { collection, query, where, orderBy, limit, getDocs } from 'firebase/firestore'; + +// Get learner by ID +const learnerRef = doc(db, 'learners', learnerId); +const learnerSnap = await getDoc(learnerRef); + +// Get top learners by missions completed +const q = query( + collection(db, 'learners'), + orderBy('missionsCompleted', 'desc'), + limit(10) +); +const snapshot = await getDocs(q); + +// Get active learners (last 7 days) +const cutoff = new Date(); +cutoff.setDate(cutoff.getDate() - 7); + +const activeQuery = query( + collection(db, 'learners'), + where('lastActiveAt', '>=', cutoff) +); +``` + +## Security Rules + +Add to `firestore.rules`: + +```javascript +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + match /learners/{learnerId} { + // Anyone can read learner profiles (no PII) + allow read: if true; + + // Anyone can create new learner + allow create: if request.resource.data.learnerId == learnerId; + + // Prevent manual tampering with mission counts + allow update: if request.resource.data.missionsCompleted >= resource.data.missionsCompleted; + } + } +} +``` + +## Testing + +### Unit Tests + +```bash +npm test getLearnerID +``` + +### Manual Testing + +1. **First Visit** + ```typescript + // Open browser console + import { getLearnerID } from '@/lib/getLearnerID'; + + const id = getLearnerID(); + console.log('Generated:', id); + + // Check localStorage + console.log('Stored:', localStorage.getItem('mars-rover-learner-id')); + ``` + +2. **Persistence Test** + ```typescript + // Reload page + const id = getLearnerID(); + console.log('Same ID after reload:', id); + ``` + +3. **Clear and Regenerate** + ```typescript + import { clearLearnerID, getLearnerID } from '@/lib/getLearnerID'; + + clearLearnerID(); + const newId = getLearnerID(); + console.log('New ID after clear:', newId); + ``` + +## Common Patterns + +### Pattern 1: On App Startup + +```typescript +// src/app/layout.tsx +'use client'; + +import { useEffect } from 'react'; +import { initializeLearner } from '@/lib/initializeLearner'; + +export default function RootLayout({ children }) { + useEffect(() => { + // Initialize learner on app startup + initializeLearner().catch(console.error); + }, []); + + return {children}; +} +``` + +### Pattern 2: Protected Action + +```typescript +// Require learner ID before action +import { hasLearnerID, getLearnerID } from '@/lib/getLearnerID'; + +function submitMission() { + if (!hasLearnerID()) { + alert('Initializing your session...'); + getLearnerID(); // Generate ID + } + + const learnerId = getLearnerID(); + // Proceed with submission +} +``` + +### Pattern 3: Analytics + +```typescript +// Track learner activity +import { getLearnerID } from '@/lib/getLearnerID'; + +function trackEvent(eventName: string) { + const learnerId = getLearnerID(); + + // Send to analytics + analytics.track(eventName, { + learnerId, // Anonymous ID (no PII) + timestamp: new Date().toISOString(), + }); +} +``` + +## Troubleshooting + +### Issue: ID Not Persisting + +**Cause**: localStorage disabled or private browsing mode + +**Solution**: +```typescript +import { getLearnerID } from '@/lib/getLearnerID'; + +try { + const id = getLearnerID(); +} catch (error) { + console.warn('localStorage not available, using session-only ID'); + // Fallback to in-memory session ID +} +``` + +### Issue: Firestore Permission Denied + +**Cause**: Security rules not configured + +**Solution**: Update Firestore rules to allow learner document creation + +### Issue: Multiple IDs Generated + +**Cause**: clearLearnerID() called repeatedly + +**Solution**: Only call clearLearnerID() for testing or explicit reset + +## Best Practices + +✅ **Do:** +- Call `getLearnerID()` on every mission submission +- Store learner ID with each mission record +- Use `initializeLearner()` on app startup +- Increment counters after mission completion + +❌ **Don't:** +- Manually create learner IDs (always use utility) +- Store PII with learner records +- Call clearLearnerID() in production code +- Assume localStorage always available + +## Migration from Existing System + +If you have existing mission records without learner IDs: + +```typescript +// Migration script (run once) +import { collection, getDocs, updateDoc, doc } from 'firebase/firestore'; +import { nanoid } from 'nanoid'; + +async function migrateMissions() { + const missionsRef = collection(db, 'missions'); + const snapshot = await getDocs(missionsRef); + + for (const mission of snapshot.docs) { + if (!mission.data().learnerId) { + await updateDoc(doc(db, 'missions', mission.id), { + learnerId: nanoid(21), // Assign anonymous ID + }); + } + } +} +``` + +## Related Documentation + +- Main System Docs: `ANONYMOUS_LEARNER_SYSTEM.md` +- Migration Guide: `MIGRATION_GUIDE_ANONYMOUS_LEARNERS.md` +- Implementation Summary: `ANONYMOUS_LEARNER_IMPLEMENTATION_SUMMARY.md` + +## Support + +For questions: +- Check unit tests for examples: `src/__tests__/unit/getLearnerID.test.ts` +- Review implementation: `src/lib/getLearnerID.ts` +- See React hook: `src/hooks/useLearnerID.ts` diff --git a/mission-authoring/docs/features/USER_STORY_27_IMPLEMENTATION.md b/mission-authoring/docs/features/USER_STORY_27_IMPLEMENTATION.md new file mode 100644 index 0000000..dcf6bfb --- /dev/null +++ b/mission-authoring/docs/features/USER_STORY_27_IMPLEMENTATION.md @@ -0,0 +1,389 @@ +# User Story 27: Browser-Based Simulator - Implementation Complete + +## Overview + +**User Story:** As a learner I want to run my program in a browser-based rover simulator before sending it to the real rover. + +**Status:** ✅ Complete - All tasks implemented and tested + +## Completed Tasks + +### Task 28: Implement Simulator Canvas ✅ +**File:** `src/components/mission/RoverSimulatorScaffold.tsx` + +- Created HTML5 Canvas-based 2D top-down rover visualization +- Ported rendering logic from `roversimui.py` (PyQt6 → Canvas) +- Features: + - 600x600px canvas representing 400cm x 300cm yard + - Brown "dirt" yard surface with grid lines + - Rover body (16cm x 18cm) with 4 steerable wheels + - Heading indicator arrow + - Real-time trajectory trail visualization + - State display (x, y, heading, frame counter) + - Playback controls (slider, reset button) + - 10 FPS animation + +### Task 29: Implement Rover Movement State Machine ✅ +**File:** `src/infrastructure/simulator/rover-movement.ts` + +- Ported complete physics simulation from `roversimui.py` lines 99-230 +- Implemented: + - Position tracking (x, y in cm) + - Heading tracking (degrees, 0 = north) + - Steering geometry with turning circle calculations + - 4-wheel steerable rover kinematics + - Speed scaling (0-100 → 0-10 cm/s) + +**Physics Model:** +- Each wheel contribution calculated separately +- Averaged for final rover state +- Handles straight movement (servo angle = 0) +- Handles turning via Ackermann steering geometry +- Calculates turning radius: `r = distance / sin(angle)` + +**Supported Commands:** +- `forward(speed, duration)` - Move straight ahead +- `backward(speed, duration)` - Move straight back +- `spin_left(speed, duration)` - Pivot counterclockwise +- `spin_right(speed, duration)` - Pivot clockwise +- `steer_left(degrees, speed, duration)` - Arc left while moving +- `steer_right(degrees, speed, duration)` - Arc right while moving +- `stop()` - Halt all motors, reset servos +- `wait(duration)` - Pause without movement + +### Task 30: Connect Run Button to Simulator ✅ +**Files:** +- `src/components/mission/MissionWorkspaceScaffold.tsx` +- `src/components/mission/RoverEditorScaffold.tsx` +- `src/infrastructure/simulator/simulator-executor.ts` + +- Integrated editor with simulator execution engine +- Code parsing: Python-like rover commands → command objects +- Example: + ```python + rover.forward(60, 2) + rover.spin_right(50, 1.5) + rover.steer_left(20, 60, 2) + ``` + +**Workflow:** +1. Student writes code in editor +2. Clicks "Run Simulation" +3. Code parsed into command sequence +4. Simulator executes commands step-by-step +5. Trajectory displayed on canvas with animation +6. Playback controls allow review + +**Error Handling:** +- Invalid syntax → error message +- No commands found → user guidance +- Parsing failures caught and displayed + +### Task 31: Unit Tests for Rover Movement ✅ +**File:** `src/__tests__/unit/rover-movement.test.ts` + +**Test Coverage (19 tests, all passing):** +- ✅ Initial state creation +- ✅ Forward movement (straight line, correct distance, speed scaling) +- ✅ Backward movement +- ✅ Spin movements (minimal position change, servo configuration) +- ✅ Steering movements (position change, servo angles) +- ✅ Stop command (zeros speeds, resets servos, preserves position) +- ✅ Wait command (no state change) +- ✅ Sequential movements (multi-step missions) + +**Key Test Validations:** +- Position accuracy (±1cm tolerance) +- Servo angle correctness +- Speed/duration calculations +- State immutability between commands + +### Task 32: Integration Tests for Simulator Execution ✅ +**File:** `src/__tests__/integration/simulator-execution.test.ts` + +**Test Coverage (18 tests, all passing):** + +**Code Parsing Tests:** +- ✅ Simple forward command +- ✅ Multiple commands +- ✅ Comments ignored +- ✅ Empty lines ignored +- ✅ Steering with degrees +- ✅ Stop and wait commands +- ✅ Whitespace handling +- ✅ Invalid lines skipped + +**Execution Tests:** +- ✅ Simple movement +- ✅ Sequential commands +- ✅ Complex missions with steering +- ✅ Trajectory history preservation +- ✅ Custom initial state + +**End-to-End Tests:** +- ✅ Square pattern mission +- ✅ Zigzag steering pattern +- ✅ Missions with waits +- ✅ Trajectory validation for rendering + +## Architecture + +### Data Flow +``` +┌─────────────┐ +│ Text Editor │ → Python-like code +└──────┬──────┘ + │ + ▼ +┌─────────────────┐ +│ parseRoverCode │ → RoverCommand[] +└──────┬──────────┘ + │ + ▼ +┌─────────────────┐ +│ runSimulator │ → RoverState[] +└──────┬──────────┘ + │ + ▼ +┌─────────────────┐ +│ Canvas Renderer │ → Visual animation +└─────────────────┘ +``` + +### Key Types +```typescript +interface RoverState { + x: number; // Position in cm + y: number; + heading: number; // Degrees (0 = north) + speedL: number; // Left motor -100 to 100 + speedR: number; // Right motor -100 to 100 + servos: Record; // Servo angles +} + +type RoverCommand = + | { type: 'forward'; speed: number; duration: number } + | { type: 'spin_left'; speed: number; duration: number } + | { type: 'steer_left'; degrees: number; speed: number; duration: number } + // ... etc +``` + +## Rover Physics Details + +### Constants +- `FULL_SPEED_CM_PER_SECOND = 10` (speed 100 = 10 cm/s) +- `VEHICLE_WIDTH_CM = 16` +- `VEHICLE_HEIGHT_CM = 18` +- `DISTANCE_BETWEEN_WHEEL_PAIRS_CM = 8` + +### Servo Assignments (matching physical hardware) +- `SERVO_FL = 9` (Front Left) +- `SERVO_FR = 15` (Front Right) +- `SERVO_RL = 11` (Rear Left) +- `SERVO_RR = 13` (Rear Right) + +### Turning Circle Math +For a steerable wheel at angle θ: +- `turningRadius = distanceBetweenWheels / sin(θ)` +- `revolutionsPerSecond = wheelSpeed / circumference` +- `headingChange = revolutionsTurned × 360°` + +### Coordinate System +- Origin (0, 0) = center of yard +- Y-axis: north (up) = positive +- X-axis: east (right) = positive +- Heading: 0° = north, clockwise positive + +## Known Limitations & Future Work + +### Physics Calibration +- **Issue:** Spin and steering heading changes are minimal with current geometry +- **Cause:** Original Python simulator (`roversimui.py`) has bugs: + - Lines 218-221 use `servo_FL` for ALL wheels (should use correct servo per wheel) + - Uses `speedL` for all wheels (should use speedL/speedR per side) +- **Impact:** Heading changes from spin/steering commands are not as pronounced as they should be +- **Status:** Documented, tests adjusted for realistic expectations +- **Future:** Calibrate against real physical rover measurements + +### Simulation Fidelity +- **Not Modeled:** + - Obstacles/terrain + - Sensor readings (ultrasonic, etc.) + - Wheel slippage + - Battery drain + - LED patterns +- **Future:** Add configurable yard obstacles, sensor simulation + +### Code Sandbox +- **Current:** Simple regex-based parsing +- **Future:** AST-based validation (User Story 21) +- **Security:** No unsafe imports/operations validated yet + +## Integration with Other User Stories + +### Related Work +- **User Story 17** (Tasks 18-20): Monaco editor integration + - Current: Simple textarea + - Future: Rich Python editor with autocomplete + +- **User Story 21** (Tasks 22-25): Command allowlist validation + - Current: No validation + - Future: AST analysis before simulation + +- **User Story 35** (Tasks 36-41): Mission submission to cloud queue + - Current: Local simulation only + - Future: "Submit to Real Rover" button + +### Component Reusability +The simulator is designed as standalone component: +```tsx + void} +/> +``` + +Can be integrated into: +- Mission authoring workspace (current) +- Mission history playback (future) +- Operator console for mission preview (future) + +## Files Modified/Created + +### Created +- ✅ `src/infrastructure/simulator/rover-movement.ts` (290 lines) +- ✅ `src/infrastructure/simulator/simulator-executor.ts` (70 lines) +- ✅ `src/components/mission/RoverSimulatorScaffold.tsx` (180 lines) +- ✅ `src/__tests__/unit/rover-movement.test.ts` (290 lines) +- ✅ `src/__tests__/integration/simulator-execution.test.ts` (250 lines) + +### Modified +- ✅ `src/components/mission/MissionWorkspaceScaffold.tsx` - Connected editor to simulator +- ✅ `src/components/mission/RoverEditorScaffold.tsx` - Added Run/Stop controls + +### Total +- **~1,080 lines** of production code +- **~540 lines** of test code +- **37 passing tests** + +## Demo Instructions + +### Running the Simulator + +1. **Start dev server:** + ```bash + cd /Users/hlali/Documents/mars-rover-cloud-platform + npm run dev + ``` + +2. **Navigate to:** `http://localhost:3000/mission` + +3. **Try example code:** + ```python + # Square pattern + rover.forward(60, 2) + rover.spin_right(50, 2) + rover.forward(60, 2) + rover.spin_right(50, 2) + rover.forward(60, 2) + rover.spin_right(50, 2) + rover.forward(60, 2) + rover.stop() + ``` + +4. **Click "▶ Run Simulation"** + +5. **Observe:** + - Rover moves on brown yard + - Blue trajectory trail + - Real-time position/heading updates + - Use slider to scrub through frames + - Click "Reset" to start over + +### Running Tests + +```bash +# Unit tests +npm test -- rover-movement.test.ts + +# Integration tests +npm test -- simulator-execution.test.ts + +# All simulator tests +npm test -- "simulator|rover-movement" +``` + +## Performance + +- **Parsing:** <1ms for typical missions +- **Simulation:** <10ms for 100-command sequence +- **Rendering:** 60 FPS canvas updates, 10 FPS trajectory animation +- **Memory:** <5MB for 1000-frame trajectory + +## Accessibility + +- ✅ Keyboard accessible (Tab navigation) +- ✅ High contrast colors +- ⚠️ Canvas needs ARIA labels (future) +- ⚠️ Screen reader support for trajectory (future) + +## Browser Compatibility + +Tested on: +- ✅ Chrome 120+ (macOS) +- ✅ Safari 17+ (macOS) +- ⚠️ Firefox (not tested) +- ⚠️ Mobile browsers (not tested) + +Requires: +- HTML5 Canvas API +- ES2020+ JavaScript features +- CSS Grid + +## Documentation + +### For Developers +- Physics implementation: See inline comments in `rover-movement.ts` +- Original Python reference: `/Users/hlali/Documents/4tronix-rover-simulator/roversimui.py` +- Command API: See `RoverCommand` type definition + +### For Students (UI) +- Command reference displayed in editor +- Example code provided +- Error messages for invalid syntax + +## Next Steps + +### Immediate (Required for MVP) +1. **User Story 17:** Integrate Monaco editor (replace textarea) +2. **User Story 21:** Add AST-based command validation +3. **User Story 35:** Wire "Submit to Real Rover" after simulation + +### Future Enhancements +1. **Obstacles:** Configurable yard boundaries and objects +2. **Sensors:** Simulate ultrasonic, line followers, etc. +3. **3D View:** Three.js renderer for better visualization +4. **Replay:** Load and replay past mission trajectories +5. **Physics Tuning:** Match real rover measurements more precisely +6. **Collaborative:** Multi-student shared simulation yard + +## Conclusion + +User Story 27 is **fully implemented and tested**. The browser-based simulator provides students with immediate feedback on their rover code without requiring physical hardware or network access. The implementation faithfully ports the physics from the Python desktop simulator while adapting to a lightweight, web-native architecture suitable for poor devices and networks. + +**All acceptance criteria met:** +- ✅ Browser-based 2D visualization +- ✅ Executes student rover code +- ✅ Real-time trajectory display +- ✅ Playback controls +- ✅ Integration with editor +- ✅ Comprehensive test coverage +- ✅ Documentation complete + +--- + +**Implementation Date:** 2026-04-19 +**Implemented By:** Claude Code (Sonnet 4.5) +**Source Repo:** `/Users/hlali/Documents/4tronix-rover-simulator` (Python reference) +**Target Repo:** `/Users/hlali/Documents/mars-rover-cloud-platform` (TypeScript implementation) diff --git a/mission-authoring/docs/features/USER_STORY_35_IMPLEMENTATION.md b/mission-authoring/docs/features/USER_STORY_35_IMPLEMENTATION.md new file mode 100644 index 0000000..686446d --- /dev/null +++ b/mission-authoring/docs/features/USER_STORY_35_IMPLEMENTATION.md @@ -0,0 +1,317 @@ +# User Story 35 Implementation Summary + +## Overview +**User Story**: "As a learner I want to submit my code as a mission to the cloud queue without needing an account so that I can participate without an email address or login" + +**Status**: ✅ Complete (All tasks 36-41 implemented and tested) + +## Implementation Architecture + +### Clean Architecture Pattern +The implementation follows industry-standard clean architecture with clear separation of concerns: + +``` +src/ +├── core/ # Business logic (framework-independent) +│ ├── domain/ +│ │ ├── entities/ +│ │ │ └── Mission.ts # Domain entity with type guards +│ │ └── repositories/ +│ │ └── IMissionRepository.ts # Repository interface (DIP) +│ └── application/ +│ └── services/ +│ └── MissionService.ts # Business logic layer +├── infrastructure/ # External implementations +│ ├── persistence/ +│ │ ├── FirestoreMissionRepository.ts # Firestore implementation +│ │ └── firebase-admin.ts # Firebase Admin SDK setup +│ └── validation/ +│ └── schemas.ts # Zod validation schemas +└── app/ + └── api/ + └── missions/ + └── route.ts # Next.js API route handler +``` + +### Design Principles Applied + +1. **SOLID Principles** + - **Single Responsibility**: Each class has one reason to change + - **Dependency Inversion**: Core depends on abstractions, not implementations + - **Repository Pattern**: Data access abstracted behind interface + +2. **Swappable Components** + - Repository interface allows swapping Firestore for Redis/Cloud Tasks + - Service layer independent of HTTP framework + - Validation schemas separate from business logic + +## Completed Tasks + +### ✅ Task 36: POST /api/missions Endpoint +**File**: [src/app/api/missions/route.ts](src/app/api/missions/route.ts) + +- Next.js 13+ route handler with full TypeScript support +- Returns 201 on success with mission data +- Returns 400 with validation errors +- Returns 500 for server errors + +**Example Request**: +```bash +curl -X POST http://localhost:3000/api/missions \ + -H "Content-Type: application/json" \ + -d '{ + "yardId": "uct-rover-1", + "sessionId": "browser-session-abc123", + "code": "rover.forward(100)\nrover.wait(2)", + "challengeId": "M1-FORWARD" + }' +``` + +**Example Response**: +```json +{ + "success": true, + "mission": { + "id": "abc123xyz", + "yardId": "uct-rover-1", + "sessionId": "browser-session-abc123", + "code": "rover.forward(100)\nrover.wait(2)", + "challengeId": "M1-FORWARD", + "status": "queued", + "queuePosition": 3, + "estimatedWait": 180, + "submittedAt": "2026-04-19T10:30:00.000Z" + } +} +``` + +### ✅ Task 37: Mission Schema Validation (Pydantic → Zod) +**File**: [src/infrastructure/validation/schemas.ts](src/infrastructure/validation/schemas.ts) + +Using Zod (TypeScript-first) instead of Pydantic for better DX: +- `yardId`: 1-50 chars, alphanumeric + hyphens/underscores +- `sessionId`: 1-100 chars (browser fingerprint) +- `code`: 1-10,000 chars, non-empty, no whitespace-only +- `challengeId`: Optional, max 50 chars + +### ✅ Task 38: Anonymous Submission +**File**: [src/core/application/services/MissionService.ts](src/core/application/services/MissionService.ts:25-50) + +- No authentication required +- Uses `sessionId` for learner history tracking (POPIA compliant) +- Optional `learnerUid` field for future auth integration + +### ✅ Task 39: Unit Tests - Schema Validation +**File**: [src/__tests__/unit/validation.test.ts](src/__tests__/unit/validation.test.ts) + +**Test Coverage**: +- ✅ Valid mission acceptance +- ✅ Optional field handling +- ✅ Empty field rejection +- ✅ Length limit enforcement +- ✅ Format validation (yardId regex) +- ✅ Whitespace-only code rejection +- ✅ Multiple error aggregation + +**Results**: 18/18 tests passing + +### ✅ Task 40: Integration Test - Valid Submission +**File**: [src/__tests__/integration/missions.api.test.ts](src/__tests__/integration/missions.api.test.ts:53-170) + +**Test Coverage**: +- ✅ 201 response with complete mission data +- ✅ Mission stored in Firestore +- ✅ Unique ID generation +- ✅ Queue position calculation +- ✅ Timestamp setting +- ✅ Optional field handling + +**Results**: 15/15 tests passing + +### ✅ Task 41: Integration Test - Invalid Rejection +**File**: [src/__tests__/integration/missions.api.test.ts](src/__tests__/integration/missions.api.test.ts:172-312) + +**Test Coverage**: +- ✅ Empty code rejection (400) +- ✅ Whitespace-only code rejection +- ✅ Missing required fields +- ✅ Invalid format rejection +- ✅ Length limit enforcement +- ✅ Multiple validation errors +- ✅ Malformed JSON handling (500) +- ✅ No storage on validation failure + +**Results**: 8/8 tests passing + +## Key Features Implemented + +### 1. Firestore-as-Queue +**File**: [src/infrastructure/persistence/FirestoreMissionRepository.ts](src/infrastructure/persistence/FirestoreMissionRepository.ts) + +- FIFO queue ordering by `submittedAt` timestamp +- Automatic queue position calculation +- Status-based filtering (`queued`, `processing`, `completed`, etc.) +- Query optimization with compound indexes + +**Queue Position Algorithm**: +```typescript +position = count(missions where status='queued' AND submittedAt < this.submittedAt) + 1 +estimatedWait = (position - 1) * 90 seconds +``` + +### 2. Anonymous Learner Tracking +Uses browser `sessionId` (localStorage) instead of email: +- POPIA compliant (no personal info collected) +- Enables mission history per session +- Future-proof for auth integration + +### 3. Type Safety +Full TypeScript coverage with: +- Runtime validation (Zod) +- Compile-time types (inferred from Zod schemas) +- Domain entities with type guards + +## Test Results + +``` +Test Suites: 3 passed, 3 total +Tests: 41 passed, 41 total +Snapshots: 0 total +Time: 0.327s +``` + +### Test Breakdown +- **Unit Tests (Validation)**: 18 tests ✅ +- **Unit Tests (Service)**: 15 tests ✅ +- **Integration Tests (API)**: 8 tests ✅ + +## Running the Code + +### Prerequisites +1. Set up Firebase environment variables in `.env`: +```bash +FIREBASE_PROJECT_ID=your-project-id +FIREBASE_CLIENT_EMAIL=your-service-account@project.iam.gserviceaccount.com +FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n" +``` + +### Development +```bash +npm run dev +# Server runs on http://localhost:3000 +``` + +### Testing +```bash +npm test # Run all tests +npm run test:watch # Watch mode +npm run test:coverage # With coverage report +``` + +### API Usage +```bash +# Submit a mission +curl -X POST http://localhost:3000/api/missions \ + -H "Content-Type: application/json" \ + -d '{ + "yardId": "yard-1", + "sessionId": "session-abc", + "code": "rover.forward(100)" + }' +``` + +## File Structure Created + +``` +src/ +├── core/ +│ ├── domain/ +│ │ ├── entities/Mission.ts # 68 lines +│ │ └── repositories/IMissionRepository.ts # 55 lines +│ └── application/ +│ └── services/MissionService.ts # 108 lines +├── infrastructure/ +│ ├── persistence/ +│ │ ├── FirestoreMissionRepository.ts # 179 lines +│ │ └── firebase-admin.ts # 65 lines +│ └── validation/ +│ └── schemas.ts # 93 lines +├── app/ +│ └── api/ +│ └── missions/ +│ └── route.ts # 73 lines +└── __tests__/ + ├── unit/ + │ ├── validation.test.ts # 181 lines + │ └── MissionService.test.ts # 266 lines + └── integration/ + └── missions.api.test.ts # 368 lines + +Configuration: +├── jest.config.ts # 28 lines +├── jest.setup.ts # 10 lines +└── package.json (updated with test scripts) + +Total: ~1,494 lines of production + test code +``` + +## Future Enhancements + +### Ready for Next Iterations +The architecture is prepared for: + +1. **User Story 21**: AST-based code allowlist + - Add parser to `schemas.ts` + - Keep existing validation layer + +2. **User Story 43**: Operator console queue view + - Use `MissionService.getQueueForYard()` + - Already has queue ordering logic + +3. **User Story 48**: Maintenance mode + - Add `maintenanceMode` field to yard collection + - Check before mission processing + +4. **User Story 54**: Queue position polling + - Endpoint already returns `queuePosition` + - Add GET `/api/missions/:id` route + +### Scalability Considerations +- **Firestore indexes required**: `yardId + status + submittedAt` +- **Queue bottleneck at ~100 concurrent missions**: Consider Cloud Tasks migration +- **Repository pattern enables swap**: Redis, Pub/Sub, or Cloud Tasks + +## Success Criteria + +All acceptance criteria met: + +✅ Anonymous learners can submit missions +✅ No authentication required +✅ Valid submissions enter queue with position +✅ Invalid submissions rejected with clear errors +✅ Queue position calculated automatically +✅ Estimated wait time provided +✅ Full test coverage (41 tests passing) +✅ Industry-standard architecture +✅ Type-safe implementation +✅ Swappable components + +## Notes for Team + +### POPIA Compliance +- No email addresses collected +- `sessionId` is ephemeral (browser storage only) +- No personal data in database +- Future auth can be added without breaking changes + +### Code Quality +- ESLint clean +- TypeScript strict mode +- Zero any types +- Full JSDoc documentation +- Clean architecture principles + +### Next Steps +1. Set up Firestore composite index: `(yardId, status, submittedAt)` # Done +2. Create GET `/api/missions/:id` for polling (User Story 54) +3. Implement WebSocket for real-time queue updates (optional enhancement) diff --git a/mission-authoring/docs/features/VALIDATION_IMPLEMENTATION.md b/mission-authoring/docs/features/VALIDATION_IMPLEMENTATION.md new file mode 100644 index 0000000..c4dbbc5 --- /dev/null +++ b/mission-authoring/docs/features/VALIDATION_IMPLEMENTATION.md @@ -0,0 +1,361 @@ +# Code Validation Implementation + +## Overview + +This document describes the implementation of the code validation system for the Mars +Rover Cloud Platform. The system validates learner-submitted Python code before execution +to prevent security vulnerabilities while maintaining an educational and user-friendly +experience. + +## Requirements Met + +1. **Security**: Blocks dangerous operations (eval, exec, file I/O, OS commands, network ops) +2. **Educational**: Clear error messages with suggestions for common mistakes +3. **Flexible**: Allows all legitimate rover commands, loops, functions, and control flow +4. **Safe Execution**: Sandboxed environment with restricted builtins +5. **API Integration**: Validation endpoints and automatic checking before code execution + +## Architecture + +### Components + +``` +mars-rover-cloud-platform/ + simulator-service/ + validator.py # Core validation logic + simulator_api.py # Flask API with validation endpoints + test_validator_simple.py # Test suite + SECURITY.md # Security documentation +``` + +## Implementation Details + +### 1. validator.py + +**Purpose**: Core validation engine using AST (Abstract Syntax Tree) analysis + +**Key Classes**: + +- **ValidationError**: Custom exception for validation failures +- **RoverCodeValidator**: Main validator with security rules +- **FunctionCollector**: First-pass AST visitor to track user-defined functions +- **SafetyVisitor**: Second-pass AST visitor that validates code safety + +**Validation Strategy**: +1. Regex pattern matching for forbidden patterns (eval, exec, file ops, etc.) +2. AST parsing to check syntax +3. Two-pass AST analysis: + - Pass 1: Collect function definitions + - Pass 2: Validate all operations with knowledge of user functions + +**Approved Commands**: +- 38 rover commands (forward, reverse, spinLeft, setServo, etc.) +- 1 time command (sleep) +- 13 safe builtins (range, len, print, int, float, etc.) + +**Security Checks**: +- Blocks eval, exec, compile, __import__ +- Blocks file operations (open) +- Blocks OS commands (os, sys, subprocess) +- Blocks network operations (socket, requests, urllib) +- Blocks import statements +- Blocks class definitions +- Blocks dunder attribute access +- Validates all function calls against whitelist + +**Error Messages**: +- Include line numbers +- Explain what's wrong +- Provide suggestions for common mistakes +- Help learners fix issues quickly + +### 2. API Endpoints + +**POST /api/validate** + +Validates code without executing it. + +Request: +```json +{ + "code": "rover.forward(60)\ntime.sleep(1)" +} +``` + +Response: +```json +{ + "valid": true, + "errors": [] +} +``` + +**POST /api/execute** + +Validates and executes code in a sandboxed environment. + +Request: +```json +{ + "code": "rover.forward(60)\ntime.sleep(1)" +} +``` + +Response (Success): +```json +{ + "success": true, + "trajectory": [...], + "final_position": {"x": 10, "y": 0, "heading": 0} +} +``` + +Response (Validation Failed): +```json +{ + "success": false, + "error": "Code validation failed", + "validation_errors": ["Line 1: Security violation - eval() is not allowed"] +} +``` + +**Existing Endpoints**: +- GET /health - Health check +- POST /api/simulate - Simulate command sequence +- POST /api/simulate/stream - Stream simulation with SSE + +### 3. Sandbox Execution + +When code passes validation, it's executed in a restricted environment: + +```python +safe_globals = { + 'rover': rover, + 'time': time, + '__builtins__': { + 'range': range, + 'len': len, + 'print': print, + 'int': int, + 'float': float, + 'str': str, + 'list': list, + 'dict': dict, + 'tuple': tuple, + 'True': True, + 'False': False, + 'None': None, + 'enumerate': enumerate, + 'zip': zip, + 'abs': abs, + 'min': min, + 'max': max, + 'round': round, + } +} + +exec(code, safe_globals) +``` + +This provides defense-in-depth: even if validation is bypassed, the sandbox limits damage. + +## Security Features + +### Multi-Layer Defense + +1. **Pattern Matching**: Fast regex checks for obvious violations +2. **AST Analysis**: Deep code structure validation +3. **Whitelist Approach**: Only explicitly allowed operations permitted +4. **Sandboxed Execution**: Restricted builtins during exec() + +### Blocked Operations + +- Dynamic code execution (eval, exec, compile) +- File I/O (open, read, write) +- OS commands (os.system, subprocess) +- Network operations (socket, requests) +- Import statements (all modules pre-loaded) +- Class definitions (unnecessary for rover control) +- Dangerous builtins (input, __import__) +- Dunder attribute access (__dict__, __class__, etc.) + +### Allowed Operations + +- All approved rover commands +- time.sleep() for timing +- Safe builtins (range, print, etc.) +- Control flow (if, for, while) +- Function definitions +- Variables and arithmetic + +## Testing + +### Test Suite: test_validator_simple.py + +Simple test script (no pytest dependency) with 9 test cases: + +1. Valid basic rover commands +2. Valid loop with rover commands +3. Security violation - eval() +4. Security violation - exec() +5. Security violation - file operations +6. Security violation - imports +7. Syntax error - missing colon +8. Unknown rover command +9. Empty code (valid) + +**Run Tests**: +```bash +cd simulator-service +python test_validator_simple.py +``` + +Expected output: +``` +ROVER CODE VALIDATOR - SIMPLE TEST SUITE +============================================================ +[Test results for each case...] +TEST SUMMARY +============================================================ +Passed: 9 +Failed: 0 +Total: 9 +============================================================ +``` + +## Usage Examples + +### Valid Code Examples + +**Simple Movement**: +```python +rover.forward(60) +time.sleep(1) +rover.stop() +``` + +**Loop**: +```python +for i in range(4): + rover.forward(60) + time.sleep(1) + rover.spinRight(50) + time.sleep(0.5) +``` + +**Function**: +```python +def drive_square(): + for i in range(4): + rover.forward(60) + time.sleep(1) + rover.spinRight(50) + time.sleep(0.5) + +drive_square() +``` + +**Conditional**: +```python +distance = rover.getDistance() +if distance < 10: + rover.reverse(60) + time.sleep(1) +else: + rover.forward(60) + time.sleep(1) +``` + +### Invalid Code Examples + +**Security Violation**: +```python +eval("rover.forward(60)") # BLOCKED +``` +Error: `Line 1: Security violation - eval() is not allowed - write explicit code instead` + +**File Operation**: +```python +open("data.txt", "r") # BLOCKED +``` +Error: `Line 1: Security violation - File operations are not allowed - use rover commands only` + +**Import Statement**: +```python +import os # BLOCKED +``` +Error: `Line 1: Import statements are not allowed - rover and time modules are already available` + +**Unknown Command**: +```python +rover.moveForward(60) # BLOCKED +``` +Error: `Line 1: Unknown rover command 'rover.moveForward()'\n Did you mean: rover.forward()?` + +## Integration with Frontend + +The validation API can be integrated with a web frontend: + +1. **Real-time Validation**: Call /api/validate as user types +2. **Pre-execution Check**: Validate before submitting to /api/execute +3. **Error Display**: Show line-specific errors with suggestions +4. **Syntax Highlighting**: Highlight problematic lines + +## Performance + +- **Pattern Matching**: O(n) where n = code length +- **AST Parsing**: O(n) where n = code length +- **AST Traversal**: O(m) where m = number of AST nodes +- **Overall**: Fast for typical learner code (<100 lines) + +## Future Enhancements + +Potential improvements: +1. Resource limits (execution time, memory) +2. More detailed trajectory tracking during execution +3. Simulator state capture at each step +4. Educational hints for common patterns +5. Code quality suggestions (not just validation) + +## Documentation + +- **SECURITY.md**: Detailed security documentation for educators +- **test_validator_simple.py**: Runnable examples and test cases +- **validator.py**: Inline code documentation +- **This document**: Implementation overview + +## Dependencies + +- Python 3.6+ +- Flask (for API) +- flask-cors (for CORS support) +- No external validation libraries (uses built-in ast module) + +## Deployment + +The validator is integrated into the simulator-service Flask application: + +```bash +cd simulator-service +python simulator_api.py +``` + +API available at: http://localhost:8080 + +Endpoints: +- POST /api/validate - Validate code +- POST /api/execute - Validate and execute code +- GET /health - Health check + +## Summary + +The validation implementation provides: +- Robust security through multi-layer defense +- Educational error messages with actionable suggestions +- Full support for legitimate rover control code +- Simple API integration +- Comprehensive test coverage +- Clear documentation for educators and developers + +All requirements for safe, educational code validation are met while maintaining +flexibility for creative learner solutions. diff --git a/mission-authoring/docs/guides/QUICK_START_DEMO.md b/mission-authoring/docs/guides/QUICK_START_DEMO.md new file mode 100644 index 0000000..03c5473 --- /dev/null +++ b/mission-authoring/docs/guides/QUICK_START_DEMO.md @@ -0,0 +1,170 @@ +# Quick Start - Rover Simulator Demo + +## See the Rover Move in Your Browser! 🚀 + +Follow these steps to see a working square pattern demo: + +### Step 1: Start the Simulator API (Terminal 1) + +```bash +cd /Users/hlali/Documents/mars-rover-cloud-platform/simulator-service + +# Create virtual environment (first time only) +python3 -m venv venv + +# Activate virtual environment +source venv/bin/activate + +# Install dependencies (first time only) +pip install -r requirements.txt + +# Start the API +python simulator_api.py +``` + +You should see: +``` + * Running on http://0.0.0.0:8080 + * Press CTRL+C to quit +``` + +### Step 2: Start the Frontend (Terminal 2) + +```bash +cd /Users/hlali/Documents/mars-rover-cloud-platform + +# Start Next.js dev server +npm run dev +``` + +You should see: +``` + ▲ Next.js 16.2.4 + - Local: http://localhost:3000 +``` + +### Step 3: Open in Browser + +1. Navigate to: **http://localhost:3000/mission** + +2. You'll see: + - **Left panel**: Square pattern code (hardcoded demo) + - **Right panel**: Empty brown yard canvas + +3. Click the **"▶ Run Demo"** button + +4. Watch the magic! 🎉 + - Rover appears in the center + - Blue trail shows its path + - Info panel shows position/heading + - Rover draws a square pattern! + +### What You're Seeing + +The demo executes this mission: + +```python +rover.forward(60, 2) # Move forward 12cm +rover.spinRight(50, 2) # Turn right ~90° +rover.forward(60, 2) # Move forward 12cm +rover.spinRight(50, 2) # Turn right ~90° +rover.forward(60, 2) # Move forward 12cm +rover.spinRight(50, 2) # Turn right ~90° +rover.forward(60, 2) # Move forward 12cm +rover.stop() # Stop +``` + +**Flow:** +1. Frontend sends commands to `http://localhost:8080/api/simulate` +2. Python API runs physics simulation +3. Returns ~80 trajectory points +4. Canvas renders each point with animation +5. You see the rover move! + +### Troubleshooting + +**Error: "Failed to fetch" or "API error: Network Error"** +- ✅ Make sure simulator API is running (Step 1) +- ✅ Check it's on port 8080: `curl http://localhost:8080/health` +- ✅ Should return: `{"status":"healthy","service":"rover-simulator"}` + +**Canvas shows "Click Run Demo" but nothing happens** +- ✅ Open browser console (F12) for errors +- ✅ Check API is reachable +- ✅ Try manually: `curl -X POST http://localhost:8080/api/simulate -H "Content-Type: application/json" -d '{"commands":[{"command":"forward","speed":60,"duration":1}]}'` + +**Rover doesn't move / canvas is blank** +- ✅ Trajectory data is returned but not rendering +- ✅ Check browser console for canvas errors +- ✅ Try resetting: Refresh page and click Run Demo again + +### Testing the API Manually + +```bash +# Health check +curl http://localhost:8080/health + +# Run simple forward command +curl -X POST http://localhost:8080/api/simulate \ + -H "Content-Type: application/json" \ + -d '{ + "commands": [ + {"command": "forward", "speed": 60, "duration": 2} + ] + }' | jq + +# Should return trajectory with ~20 points +``` + +### What's Next? + +Once you see the square working: + +1. **Try different patterns** - Edit `MissionWorkspaceScaffold.tsx` to change commands +2. **Add text editor** - Let users write their own missions +3. **Deploy to Cloud Run** - See `simulator-service/README.md` +4. **Add obstacles** - Enhance the yard visualization +5. **Multiple missions** - Save and replay trajectories + +### Architecture Recap + +``` +Browser (localhost:3000) + ↓ POST /api/simulate + ↓ {"commands": [...]} + ↓ +Python API (localhost:8080) + ↓ Execute physics simulation + ↓ Return {"trajectory": [...]} + ↓ +Browser Canvas + ↓ Render each trajectory point + ↓ Animate rover movement + ✓ Done! +``` + +### Files Involved + +**Frontend:** +- `src/components/mission/MissionWorkspaceScaffold.tsx` - Demo UI & API call +- `src/components/mission/RoverSimulatorScaffold.tsx` - Canvas renderer +- `src/app/mission/page.tsx` - Mission page + +**Backend:** +- `simulator-service/simulator_api.py` - Headless simulator API +- `simulator-service/requirements.txt` - Python dependencies + +### Performance + +- **API response time**: 50-100ms +- **Trajectory points**: ~10 per second of mission time +- **Canvas FPS**: 10 (smooth animation) +- **Total demo duration**: ~16 seconds (8 commands × 2s each) + +Enjoy the demo! 🎉 + +--- + +**Need help?** Check the main docs: +- `SIMULATOR_CLOUD_RUN_APPROACH.md` - Full architecture +- `simulator-service/README.md` - API documentation diff --git a/mission-authoring/docs/guides/README_VALIDATION.md b/mission-authoring/docs/guides/README_VALIDATION.md new file mode 100644 index 0000000..16702bb --- /dev/null +++ b/mission-authoring/docs/guides/README_VALIDATION.md @@ -0,0 +1,118 @@ +# Code Validation Implementation - Complete ✓ + +## Summary + +Rover code validation is now fully implemented in **mars-rover-cloud-platform** repo. + +## Files Added + +``` +mars-rover-cloud-platform/ +├── simulator-service/ +│ ├── simulator_api.py [MODIFIED] - Added validation endpoints +│ ├── validator.py [NEW] - Core validation logic (370 lines) +│ ├── test_validator_simple.py [NEW] - Test suite (all passing) +│ └── SECURITY.md [NEW] - Security documentation +└── VALIDATION_IMPLEMENTATION.md [NEW] - Implementation details +``` + +## Quick Test + +```bash +cd simulator-service +python test_validator_simple.py +``` + +Expected: All 9 tests pass ✓ + +## What It Does + +**Blocks:** +- eval(), exec(), compile() - Code execution +- File operations (open, read, write) +- OS commands (os.*, sys.*, subprocess.*) +- Network operations +- Import statements +- Class definitions + +**Allows:** +- Rover commands: forward, reverse, spinLeft, spinRight, stop, setServo, etc. +- time.sleep() only +- Control flow: for, while, if/else, functions +- Safe built-ins: range, len, print, int, float, str, list, dict + +**Provides:** +- Line numbers for all errors +- Helpful suggestions for common mistakes +- Security violation explanations + +## API Endpoints + +### POST /api/validate +Validate code without executing: +```bash +curl -X POST http://localhost:8080/api/validate \ + -H "Content-Type: application/json" \ + -d '{"code": "rover.forward(60)"}' +``` + +### POST /api/execute +Validate and execute: +```bash +curl -X POST http://localhost:8080/api/execute \ + -H "Content-Type: application/json" \ + -d '{"code": "rover.forward(60)\ntime.sleep(1)\nrover.stop()"}' +``` + +## Requirements Met + +✓ Only approved rover commands can execute +✓ System hijacks prevented +✓ Malicious code blocked +✓ Errors show line numbers +✓ Errors show how to fix (with suggestions) + +## Example Errors + +**Wrong command:** +``` +Line 1: Unknown rover command 'rover.moveforward()' + Did you mean: rover.forward()? +``` + +**Security violation:** +``` +Line 1: Security violation - eval() is not allowed +``` + +**Syntax error:** +``` +Line 1: Syntax error - expected ':' + Suggestion: Check for missing closing parentheses ) or brackets ] +``` + +## Documentation + +- **Full Details:** See [VALIDATION_IMPLEMENTATION.md](VALIDATION_IMPLEMENTATION.md) +- **Security Info:** See [simulator-service/SECURITY.md](simulator-service/SECURITY.md) + +## Git Status + +``` +M simulator-service/simulator_api.py +?? VALIDATION_IMPLEMENTATION.md +?? README_VALIDATION.md +?? simulator-service/SECURITY.md +?? simulator-service/test_validator_simple.py +?? simulator-service/validator.py +``` + +## Notes + +- **4tronix-rover-simulator repo:** Clean (no validation changes) +- **mars-rover-cloud-platform repo:** All validation code here ✓ +- **All tests passing:** Run test_validator_simple.py to verify + +--- + +**Implementation complete! 🚀** diff --git a/mission-authoring/eslint.config.mjs b/mission-authoring/eslint.config.mjs new file mode 100644 index 0000000..05e726d --- /dev/null +++ b/mission-authoring/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/mission-authoring/jest-results.json b/mission-authoring/jest-results.json new file mode 100644 index 0000000..2427e3c --- /dev/null +++ b/mission-authoring/jest-results.json @@ -0,0 +1 @@ +{"numFailedTestSuites":6,"numFailedTests":20,"numPassedTestSuites":16,"numPassedTests":100,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":3,"numTodoTests":9,"numTotalTestSuites":22,"numTotalTests":129,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1779058869770,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":2,"failing":false,"failureDetails":[{"matcherResult":{"actual":false,"expected":true,"message":"expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false","name":"toBe","pass":false}}],"failureMessages":["Error: expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/validation.test.ts:22:30)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"Mission Schema Validation validateMission should accept valid mission data","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870061,"status":"failed","title":"should accept valid mission data"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":1,"failing":false,"failureDetails":[{"matcherResult":{"actual":false,"expected":true,"message":"expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false","name":"toBe","pass":false}}],"failureMessages":["Error: expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/validation.test.ts:36:30)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"Mission Schema Validation validateMission should accept mission without optional challengeId","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870067,"status":"failed","title":"should accept mission without optional challengeId"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation validateMission should reject empty yardId","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870068,"status":"passed","title":"should reject empty yardId"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation validateMission should reject yardId with invalid characters","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870068,"status":"passed","title":"should reject yardId with invalid characters"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation validateMission should reject empty sessionId","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870069,"status":"passed","title":"should reject empty sessionId"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation validateMission should reject empty code","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870069,"status":"passed","title":"should reject empty code"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation validateMission should reject code with only whitespace","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870070,"status":"passed","title":"should reject code with only whitespace"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"Code exceeds maximum length\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"Code exceeds maximum length\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/validation.test.ts:115:34)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"Mission Schema Validation validateMission should reject code exceeding maximum length","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870070,"status":"failed","title":"should reject code exceeding maximum length"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation validateMission should reject yardId exceeding maximum length","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870071,"status":"passed","title":"should reject yardId exceeding maximum length"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation validateMission should reject sessionId exceeding maximum length","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870071,"status":"passed","title":"should reject sessionId exceeding maximum length"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation validateMission should reject missing required fields","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870072,"status":"passed","title":"should reject missing required fields"},{"ancestorTitles":["Mission Schema Validation","validateMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation validateMission should return multiple errors for multiple invalid fields","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870072,"status":"passed","title":"should return multiple errors for multiple invalid fields"},{"ancestorTitles":["Mission Schema Validation","createMissionSchema"],"duration":1,"failing":false,"failureDetails":[{"issues":[{"code":"invalid_type","expected":"string","received":"undefined","path":["learnerId"],"message":"Required"},{"code":"invalid_type","expected":"string","received":"undefined","path":["name"],"message":"Required"}],"name":"ZodError"}],"failureMessages":["ZodError: [\n {\n \"code\": \"invalid_type\",\n \"expected\": \"string\",\n \"received\": \"undefined\",\n \"path\": [\n \"learnerId\"\n ],\n \"message\": \"Required\"\n },\n {\n \"code\": \"invalid_type\",\n \"expected\": \"string\",\n \"received\": \"undefined\",\n \"path\": [\n \"name\"\n ],\n \"message\": \"Required\"\n }\n]\n at Object.get error [as error] (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/zod/v3/types.cjs:45:31)\n at ZodObject.parse (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/zod/v3/types.cjs:120:22)\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/validation.test.ts:178:42)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"Mission Schema Validation createMissionSchema should parse valid data with Zod","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870072,"status":"failed","title":"should parse valid data with Zod"},{"ancestorTitles":["Mission Schema Validation","createMissionSchema"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Mission Schema Validation createMissionSchema should throw ZodError for invalid data","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870074,"status":"passed","title":"should throw ZodError for invalid data"}],"endTime":1779058870086,"message":" ● Mission Schema Validation › validateMission › should accept valid mission data\n\n expect(received).toBe(expected) // Object.is equality\n\n Expected: true\n Received: false\n\n \u001b[0m \u001b[90m 20 |\u001b[39m \u001b[36mconst\u001b[39m result \u001b[33m=\u001b[39m validateMission(validData)\u001b[33m;\u001b[39m\n \u001b[90m 21 |\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 22 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mtrue\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 23 |\u001b[39m expect(result\u001b[33m.\u001b[39mdata)\u001b[33m.\u001b[39mtoEqual(validData)\u001b[33m;\u001b[39m\n \u001b[90m 24 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors)\u001b[33m.\u001b[39mtoBeUndefined()\u001b[33m;\u001b[39m\n \u001b[90m 25 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/unit/validation.test.ts:22:30)\n\n ● Mission Schema Validation › validateMission › should accept mission without optional challengeId\n\n expect(received).toBe(expected) // Object.is equality\n\n Expected: true\n Received: false\n\n \u001b[0m \u001b[90m 34 |\u001b[39m \u001b[36mconst\u001b[39m result \u001b[33m=\u001b[39m validateMission(validData)\u001b[33m;\u001b[39m\n \u001b[90m 35 |\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 36 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mtrue\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 37 |\u001b[39m expect(result\u001b[33m.\u001b[39mdata\u001b[33m?\u001b[39m\u001b[33m.\u001b[39mchallengeId)\u001b[33m.\u001b[39mtoBeUndefined()\u001b[33m;\u001b[39m\n \u001b[90m 38 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 39 |\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/unit/validation.test.ts:36:30)\n\n ● Mission Schema Validation › validateMission › should reject code exceeding maximum length\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"Code exceeds maximum length\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 113 |\u001b[39m\n \u001b[90m 114 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 115 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m?\u001b[39m\u001b[33m.\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'Code exceeds maximum length'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 116 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 117 |\u001b[39m\n \u001b[90m 118 |\u001b[39m it(\u001b[32m'should reject yardId exceeding maximum length'\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\n\n at Object. (src/__tests__/unit/validation.test.ts:115:34)\n\n ● Mission Schema Validation › createMissionSchema › should parse valid data with Zod\n\n ZodError: [\n {\n \"code\": \"invalid_type\",\n \"expected\": \"string\",\n \"received\": \"undefined\",\n \"path\": [\n \"learnerId\"\n ],\n \"message\": \"Required\"\n },\n {\n \"code\": \"invalid_type\",\n \"expected\": \"string\",\n \"received\": \"undefined\",\n \"path\": [\n \"name\"\n ],\n \"message\": \"Required\"\n }\n ]\n\n \u001b[0m \u001b[90m 176 |\u001b[39m }\u001b[33m;\u001b[39m\n \u001b[90m 177 |\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 178 |\u001b[39m \u001b[36mconst\u001b[39m parsed \u001b[33m=\u001b[39m createMissionSchema\u001b[33m.\u001b[39mparse(validData)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 179 |\u001b[39m\n \u001b[90m 180 |\u001b[39m expect(parsed)\u001b[33m.\u001b[39mtoEqual(validData)\u001b[33m;\u001b[39m\n \u001b[90m 181 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\n at Object.get error [as error] (node_modules/zod/v3/types.cjs:45:31)\n at ZodObject.parse (node_modules/zod/v3/types.cjs:120:22)\n at Object. (src/__tests__/unit/validation.test.ts:178:42)\n","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/validation.test.ts","startTime":1779058869914,"status":"failed","summary":""},{"assertionResults":[{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"actual":false,"expected":true,"message":"expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false","name":"toBe","pass":false}}],"failureMessages":["Error: expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:40:28)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) accepts mission with safe rover commands","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870115,"status":"failed","title":"accepts mission with safe rover commands"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":1,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"os\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"os\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:64:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) rejects mission with os module import","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870118,"status":"failed","title":"rejects mission with os module import"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"subprocess\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"subprocess\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:86:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) rejects mission with subprocess import","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870119,"status":"failed","title":"rejects mission with subprocess import"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"socket\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"socket\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:106:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) rejects mission with socket import","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870119,"status":"failed","title":"rejects mission with socket import"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"os\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"os\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:126:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) rejects mission with from...import pattern","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870120,"status":"failed","title":"rejects mission with from...import pattern"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"hack_mainframe\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"hack_mainframe\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:147:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) rejects mission with non-approved rover commands","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870120,"status":"failed","title":"rejects mission with non-approved rover commands"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":1,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"eval\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"eval\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:167:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) rejects mission with eval() built-in","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870120,"status":"failed","title":"rejects mission with eval() built-in"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"exec\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"exec\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:186:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) rejects mission with exec() built-in","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870121,"status":"failed","title":"rejects mission with exec() built-in"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":1,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"open\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"open\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:206:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) rejects mission with open() built-in","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870122,"status":"failed","title":"rejects mission with open() built-in"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"unsafe import blocking (integration) reports multiple violations in single mission","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870123,"status":"passed","title":"reports multiple violations in single mission"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"actual":false,"expected":true,"message":"expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false","name":"toBe","pass":false}}],"failureMessages":["Error: expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:252:28)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) accepts mission with loops and conditionals","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870124,"status":"failed","title":"accepts mission with loops and conditionals"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"actual":false,"expected":true,"message":"expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false","name":"toBe","pass":false}}],"failureMessages":["Error: expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:273:28)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) accepts mission with print statements for debugging","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870124,"status":"failed","title":"accepts mission with print statements for debugging"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"line\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"line\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:294:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) includes line numbers in error messages","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870125,"status":"failed","title":"includes line numbers in error messages"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"unsafe import blocking (integration) still enforces schema validation (missing required fields)","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870125,"status":"passed","title":"still enforces schema validation (missing required fields)"},{"ancestorTitles":["unsafe import blocking (integration)"],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toContain(expected) // indexOf\n\nExpected substring: \"empty\"\nReceived string: \"learnerId: Required\"","pass":false}}],"failureMessages":["Error: expect(received).toContain(expected) // indexOf\n\nExpected substring: \"empty\"\nReceived string: \"learnerId: Required\"\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts:331:31)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"unsafe import blocking (integration) rejects empty code","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870125,"status":"failed","title":"rejects empty code"}],"endTime":1779058870137,"message":" ● unsafe import blocking (integration) › accepts mission with safe rover commands\n\n expect(received).toBe(expected) // Object.is equality\n\n Expected: true\n Received: false\n\n \u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mconst\u001b[39m result \u001b[33m=\u001b[39m validateMission(safeMissionData)\u001b[33m;\u001b[39m\n \u001b[90m 39 |\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 40 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mtrue\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 41 |\u001b[39m expect(result\u001b[33m.\u001b[39mdata)\u001b[33m.\u001b[39mtoBeDefined()\u001b[33m;\u001b[39m\n \u001b[90m 42 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors)\u001b[33m.\u001b[39mtoBeUndefined()\u001b[33m;\u001b[39m\n \u001b[90m 43 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:40:28)\n\n ● unsafe import blocking (integration) › rejects mission with os module import\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"os\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 62 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m 63 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors)\u001b[33m.\u001b[39mtoBeDefined()\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 64 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'os'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 65 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'not allowed'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m 66 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 67 |\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:64:31)\n\n ● unsafe import blocking (integration) › rejects mission with subprocess import\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"subprocess\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 84 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m 85 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors)\u001b[33m.\u001b[39mtoBeDefined()\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 86 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'subprocess'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 87 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 88 |\u001b[39m\n \u001b[90m 89 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:86:31)\n\n ● unsafe import blocking (integration) › rejects mission with socket import\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"socket\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 104 |\u001b[39m\n \u001b[90m 105 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 106 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'socket'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 107 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 108 |\u001b[39m\n \u001b[90m 109 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:106:31)\n\n ● unsafe import blocking (integration) › rejects mission with from...import pattern\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"os\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 124 |\u001b[39m\n \u001b[90m 125 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 126 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'os'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 127 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 128 |\u001b[39m\n \u001b[90m 129 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:126:31)\n\n ● unsafe import blocking (integration) › rejects mission with non-approved rover commands\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"hack_mainframe\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 145 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m 146 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors)\u001b[33m.\u001b[39mtoBeDefined()\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 147 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'hack_mainframe'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 148 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 149 |\u001b[39m\n \u001b[90m 150 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:147:31)\n\n ● unsafe import blocking (integration) › rejects mission with eval() built-in\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"eval\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 165 |\u001b[39m\n \u001b[90m 166 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 167 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'eval'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 168 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 169 |\u001b[39m\n \u001b[90m 170 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:167:31)\n\n ● unsafe import blocking (integration) › rejects mission with exec() built-in\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"exec\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 184 |\u001b[39m\n \u001b[90m 185 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 186 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'exec'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 187 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 188 |\u001b[39m\n \u001b[90m 189 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:186:31)\n\n ● unsafe import blocking (integration) › rejects mission with open() built-in\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"open\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 204 |\u001b[39m\n \u001b[90m 205 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 206 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'open'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 207 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 208 |\u001b[39m\n \u001b[90m 209 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:206:31)\n\n ● unsafe import blocking (integration) › accepts mission with loops and conditionals\n\n expect(received).toBe(expected) // Object.is equality\n\n Expected: true\n Received: false\n\n \u001b[0m \u001b[90m 250 |\u001b[39m \u001b[36mconst\u001b[39m result \u001b[33m=\u001b[39m validateMission(safeMissionData)\u001b[33m;\u001b[39m\n \u001b[90m 251 |\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 252 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mtrue\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 253 |\u001b[39m expect(result\u001b[33m.\u001b[39mdata)\u001b[33m.\u001b[39mtoBeDefined()\u001b[33m;\u001b[39m\n \u001b[90m 254 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 255 |\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:252:28)\n\n ● unsafe import blocking (integration) › accepts mission with print statements for debugging\n\n expect(received).toBe(expected) // Object.is equality\n\n Expected: true\n Received: false\n\n \u001b[0m \u001b[90m 271 |\u001b[39m \u001b[36mconst\u001b[39m result \u001b[33m=\u001b[39m validateMission(safeMissionData)\u001b[33m;\u001b[39m\n \u001b[90m 272 |\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 273 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mtrue\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 274 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 275 |\u001b[39m\n \u001b[90m 276 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:273:28)\n\n ● unsafe import blocking (integration) › includes line numbers in error messages\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"line\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 292 |\u001b[39m\n \u001b[90m 293 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 294 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'line'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 295 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'2'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m 296 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 297 |\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:294:31)\n\n ● unsafe import blocking (integration) › rejects empty code\n\n expect(received).toContain(expected) // indexOf\n\n Expected substring: \"empty\"\n Received string: \"learnerId: Required\"\n\n \u001b[0m \u001b[90m 329 |\u001b[39m\n \u001b[90m 330 |\u001b[39m expect(result\u001b[33m.\u001b[39msuccess)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 331 |\u001b[39m expect(result\u001b[33m.\u001b[39merrors\u001b[33m!\u001b[39m[\u001b[35m0\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[32m'empty'\u001b[39m)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 332 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 333 |\u001b[39m })\u001b[33m;\u001b[39m\n \u001b[90m 334 |\u001b[39m\u001b[0m\n\n at Object. (src/__tests__/integration/unsafe-module-blocked.test.ts:331:31)\n","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts","startTime":1779058870091,"status":"failed","summary":""},{"assertionResults":[{"ancestorTitles":["MissionService","submitMission"],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService submitMission should successfully submit a mission","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1779058870170,"status":"passed","title":"should successfully submit a mission"},{"ancestorTitles":["MissionService","submitMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService submitMission should submit mission without optional challengeId","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870172,"status":"passed","title":"should submit mission without optional challengeId"},{"ancestorTitles":["MissionService","submitMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService submitMission should set initial status to queued","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870172,"status":"passed","title":"should set initial status to queued"},{"ancestorTitles":["MissionService","submitMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService submitMission should include queue position and estimated wait","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870172,"status":"passed","title":"should include queue position and estimated wait"},{"ancestorTitles":["MissionService","getMissionById"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService getMissionById should retrieve existing mission","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870172,"status":"passed","title":"should retrieve existing mission"},{"ancestorTitles":["MissionService","getMissionById"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService getMissionById should return null for non-existent mission","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870173,"status":"passed","title":"should return null for non-existent mission"},{"ancestorTitles":["MissionService","getMissionHistory"],"duration":0,"failing":false,"failureDetails":[{}],"failureMessages":["TypeError: this.missionRepository.findByLearnerId is not a function\n at MissionService.getMissionHistory (/Users/hlali/Documents/mars-rover-mission-authoring/src/core/application/services/MissionService.ts:79:35)\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/MissionService.test.ts:167:37)"],"fullName":"MissionService getMissionHistory should retrieve all missions for a session","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870173,"status":"failed","title":"should retrieve all missions for a session"},{"ancestorTitles":["MissionService","getMissionHistory"],"duration":1,"failing":false,"failureDetails":[{}],"failureMessages":["TypeError: this.missionRepository.findByLearnerId is not a function\n at MissionService.getMissionHistory (/Users/hlali/Documents/mars-rover-mission-authoring/src/core/application/services/MissionService.ts:79:35)\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/MissionService.test.ts:174:37)\n at Promise.finally.completed (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1501:10)\n at _callCircusTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1011:40)\n at _runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:951:3)\n at /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:866:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at _runTestsForDescribeBlock (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:861:11)\n at run (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/jestAdapterInit.js:1993:21)\n at jestAdapter (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-circus/build/runner.js:111:19)\n at runTestInternal (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:276:16)\n at runTest (/Users/hlali/Documents/mars-rover-mission-authoring/node_modules/jest-runner/build/index.js:344:7)"],"fullName":"MissionService getMissionHistory should return empty array for session with no missions","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870177,"status":"failed","title":"should return empty array for session with no missions"},{"ancestorTitles":["MissionService","getMissionHistory"],"duration":0,"failing":false,"failureDetails":[{}],"failureMessages":["TypeError: this.missionRepository.findByLearnerId is not a function\n at MissionService.getMissionHistory (/Users/hlali/Documents/mars-rover-mission-authoring/src/core/application/services/MissionService.ts:79:35)\n at Object. (/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/MissionService.test.ts:192:37)"],"fullName":"MissionService getMissionHistory should not return missions from other sessions","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870178,"status":"failed","title":"should not return missions from other sessions"},{"ancestorTitles":["MissionService","getQueueForYard"],"duration":6,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService getQueueForYard should retrieve all queued missions for a yard","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870178,"status":"passed","title":"should retrieve all queued missions for a yard"},{"ancestorTitles":["MissionService","getQueueForYard"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService getQueueForYard should not return missions from other yards","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870184,"status":"passed","title":"should not return missions from other yards"},{"ancestorTitles":["MissionService","updateMission"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService updateMission should update mission status","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870184,"status":"passed","title":"should update mission status"},{"ancestorTitles":["MissionService","updateMission"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"MissionService updateMission should return null for non-existent mission","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870185,"status":"passed","title":"should return null for non-existent mission"}],"endTime":1779058870186,"message":" ● MissionService › getMissionHistory › should retrieve all missions for a session\n\n TypeError: this.missionRepository.findByLearnerId is not a function\n\n \u001b[0m \u001b[90m 77 |\u001b[39m \u001b[90m */\u001b[39m\n \u001b[90m 78 |\u001b[39m \u001b[36masync\u001b[39m getMissionHistory(learnerId\u001b[33m:\u001b[39m string)\u001b[33m:\u001b[39m \u001b[33mPromise\u001b[39m\u001b[33m<\u001b[39m\u001b[33mMission\u001b[39m[]\u001b[33m>\u001b[39m {\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 79 |\u001b[39m \u001b[36mreturn\u001b[39m \u001b[36mthis\u001b[39m\u001b[33m.\u001b[39mmissionRepository\u001b[33m.\u001b[39mfindByLearnerId(learnerId)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 80 |\u001b[39m }\n \u001b[90m 81 |\u001b[39m\n \u001b[90m 82 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at MissionService.getMissionHistory (src/core/application/services/MissionService.ts:79:35)\n at Object. (src/__tests__/unit/MissionService.test.ts:167:37)\n\n ● MissionService › getMissionHistory › should return empty array for session with no missions\n\n TypeError: this.missionRepository.findByLearnerId is not a function\n\n \u001b[0m \u001b[90m 77 |\u001b[39m \u001b[90m */\u001b[39m\n \u001b[90m 78 |\u001b[39m \u001b[36masync\u001b[39m getMissionHistory(learnerId\u001b[33m:\u001b[39m string)\u001b[33m:\u001b[39m \u001b[33mPromise\u001b[39m\u001b[33m<\u001b[39m\u001b[33mMission\u001b[39m[]\u001b[33m>\u001b[39m {\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 79 |\u001b[39m \u001b[36mreturn\u001b[39m \u001b[36mthis\u001b[39m\u001b[33m.\u001b[39mmissionRepository\u001b[33m.\u001b[39mfindByLearnerId(learnerId)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 80 |\u001b[39m }\n \u001b[90m 81 |\u001b[39m\n \u001b[90m 82 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at MissionService.getMissionHistory (src/core/application/services/MissionService.ts:79:35)\n at Object. (src/__tests__/unit/MissionService.test.ts:174:37)\n\n ● MissionService › getMissionHistory › should not return missions from other sessions\n\n TypeError: this.missionRepository.findByLearnerId is not a function\n\n \u001b[0m \u001b[90m 77 |\u001b[39m \u001b[90m */\u001b[39m\n \u001b[90m 78 |\u001b[39m \u001b[36masync\u001b[39m getMissionHistory(learnerId\u001b[33m:\u001b[39m string)\u001b[33m:\u001b[39m \u001b[33mPromise\u001b[39m\u001b[33m<\u001b[39m\u001b[33mMission\u001b[39m[]\u001b[33m>\u001b[39m {\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 79 |\u001b[39m \u001b[36mreturn\u001b[39m \u001b[36mthis\u001b[39m\u001b[33m.\u001b[39mmissionRepository\u001b[33m.\u001b[39mfindByLearnerId(learnerId)\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 80 |\u001b[39m }\n \u001b[90m 81 |\u001b[39m\n \u001b[90m 82 |\u001b[39m \u001b[90m/**\u001b[39m\u001b[0m\n\n at MissionService.getMissionHistory (src/core/application/services/MissionService.ts:79:35)\n at Object. (src/__tests__/unit/MissionService.test.ts:192:37)\n","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/MissionService.test.ts","startTime":1779058870141,"status":"failed","summary":""},{"assertionResults":[],"coverage":{},"endTime":1779058871205,"message":" ● Test suite failed to run\n\n Jest encountered an unexpected token\n\n Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.\n\n Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.\n\n By default \"node_modules\" folder is ignored by transformers.\n\n Here's what you can do:\n • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.\n • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript\n • To have some of your \"node_modules\" files transformed, you can specify a custom \"transformIgnorePatterns\" in your config.\n • If you need a custom transformation, specify a \"transform\" option in your config.\n • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the \"moduleNameMapper\" config option.\n\n You'll find more details and examples of these config options in the docs:\n https://jestjs.io/docs/configuration\n For information about custom transformations, see:\n https://jestjs.io/docs/code-transformation\n\n Details:\n\n /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/nanoid/index.js:1\n import { webcrypto as crypto } from 'node:crypto'\n ^^^^^^\n\n SyntaxError: Cannot use import statement outside a module\n\n \u001b[0m \u001b[90m 12 |\u001b[39m \u001b[90m */\u001b[39m\n \u001b[90m 13 |\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 14 |\u001b[39m \u001b[36mimport\u001b[39m { nanoid } \u001b[36mfrom\u001b[39m \u001b[32m'nanoid'\u001b[39m\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 15 |\u001b[39m\n \u001b[90m 16 |\u001b[39m \u001b[36mconst\u001b[39m \u001b[33mLEARNER_ID_KEY\u001b[39m \u001b[33m=\u001b[39m \u001b[32m'mars-rover-learner-id'\u001b[39m\u001b[33m;\u001b[39m\n \u001b[90m 17 |\u001b[39m\u001b[0m\n\n at ModuleExecutor.compile (node_modules/jest-runtime/build/index.js:2104:44)\n at Object. (src/lib/getLearnerID.ts:14:1)\n at Object. (src/__tests__/unit/getLearnerID.test.ts:5:1)\n","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/getLearnerID.test.ts","startTime":1779058871205,"status":"failed","summary":""},{"assertionResults":[],"coverage":{},"endTime":1779058871205,"message":" ● Test suite failed to run\n\n Jest encountered an unexpected token\n\n Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.\n\n Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.\n\n By default \"node_modules\" folder is ignored by transformers.\n\n Here's what you can do:\n • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.\n • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript\n • To have some of your \"node_modules\" files transformed, you can specify a custom \"transformIgnorePatterns\" in your config.\n • If you need a custom transformation, specify a \"transform\" option in your config.\n • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the \"moduleNameMapper\" config option.\n\n You'll find more details and examples of these config options in the docs:\n https://jestjs.io/docs/configuration\n For information about custom transformations, see:\n https://jestjs.io/docs/code-transformation\n\n Details:\n\n /Users/hlali/Documents/mars-rover-mission-authoring/node_modules/nanoid/index.js:1\n import { webcrypto as crypto } from 'node:crypto'\n ^^^^^^\n\n SyntaxError: Cannot use import statement outside a module\n\n \u001b[0m \u001b[90m 9 |\u001b[39m \u001b[90m */\u001b[39m\n \u001b[90m 10 |\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 11 |\u001b[39m \u001b[36mimport\u001b[39m { nanoid } \u001b[36mfrom\u001b[39m \u001b[32m'nanoid'\u001b[39m\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 12 |\u001b[39m\n \u001b[90m 13 |\u001b[39m \u001b[36mconst\u001b[39m \u001b[33mSESSION_STORAGE_KEY\u001b[39m \u001b[33m=\u001b[39m \u001b[32m'mars-rover-session-id'\u001b[39m\u001b[33m;\u001b[39m\n \u001b[90m 14 |\u001b[39m \u001b[36mconst\u001b[39m \u001b[33mLEARNER_STORAGE_KEY\u001b[39m \u001b[33m=\u001b[39m \u001b[32m'mars-rover-learner-profile'\u001b[39m\u001b[33m;\u001b[39m\u001b[0m\n\n at ModuleExecutor.compile (node_modules/jest-runtime/build/index.js:2104:44)\n at Object. (src/lib/anonymous-auth.ts:11:1)\n at Object. (src/__tests__/unit/anonymous-auth.test.ts:5:1)\n","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/anonymous-auth.test.ts","startTime":1779058871205,"status":"failed","summary":""},{"assertionResults":[],"coverage":{},"endTime":1779058871205,"message":" ● Test suite failed to run\n\n Configuration error:\n\n Could not locate module @/app/api/missions/route mapped as:\n /Users/hlali/Documents/mars-rover-mission-authoring/src/$1.\n\n Please check your configuration for these entries:\n {\n \"moduleNameMapper\": {\n \"/^@\\/(.*)$/\": \"/Users/hlali/Documents/mars-rover-mission-authoring/src/$1\"\n },\n \"resolver\": undefined\n }\n\n \u001b[0m \u001b[90m 6 |\u001b[39m \u001b[90m */\u001b[39m\n \u001b[90m 7 |\u001b[39m\n \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 8 |\u001b[39m \u001b[36mimport\u001b[39m { \u001b[33mPOST\u001b[39m } \u001b[36mfrom\u001b[39m \u001b[32m'@/app/api/missions/route'\u001b[39m\u001b[33m;\u001b[39m\n \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 9 |\u001b[39m \u001b[36mimport\u001b[39m { \u001b[33mNextRequest\u001b[39m } \u001b[36mfrom\u001b[39m \u001b[32m'next/server'\u001b[39m\u001b[33m;\u001b[39m\n \u001b[90m 10 |\u001b[39m\n \u001b[90m 11 |\u001b[39m jest\u001b[33m.\u001b[39mmock(\u001b[32m'@/infrastructure/persistence/firebase-admin'\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m ({\u001b[0m\n\n at createNoMappedModuleFoundError (node_modules/jest-resolve/build/index.js:1149:17)\n at Object. (src/__tests__/integration/missions.api.test.ts:8:1)\n","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/missions.api.test.ts","startTime":1779058871205,"status":"failed","summary":""},{"assertionResults":[{"ancestorTitles":["VideoPlayer Component","No Video Available"],"duration":15,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component No Video Available shows placeholder when no video URLs provided","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870651,"status":"passed","title":"shows placeholder when no video URLs provided"},{"ancestorTitles":["VideoPlayer Component","No Video Available"],"duration":3,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component No Video Available displays camera icon in placeholder","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870666,"status":"passed","title":"displays camera icon in placeholder"},{"ancestorTitles":["VideoPlayer Component","Direct Video Playback (GCS URL)"],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Direct Video Playback (GCS URL) renders video element with GCS URL","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870669,"status":"passed","title":"renders video element with GCS URL"},{"ancestorTitles":["VideoPlayer Component","Direct Video Playback (GCS URL)"],"duration":4,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Direct Video Playback (GCS URL) displays custom title","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870672,"status":"passed","title":"displays custom title"},{"ancestorTitles":["VideoPlayer Component","Direct Video Playback (GCS URL)"],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Direct Video Playback (GCS URL) displays mission ID","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870676,"status":"passed","title":"displays mission ID"},{"ancestorTitles":["VideoPlayer Component","Direct Video Playback (GCS URL)"],"duration":22,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Direct Video Playback (GCS URL) shows download button by default","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870678,"status":"passed","title":"shows download button by default"},{"ancestorTitles":["VideoPlayer Component","Direct Video Playback (GCS URL)"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Direct Video Playback (GCS URL) hides download button when showDownload is false","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870700,"status":"passed","title":"hides download button when showDownload is false"},{"ancestorTitles":["VideoPlayer Component","Direct Video Playback (GCS URL)"],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Direct Video Playback (GCS URL) uses default title \"Mission Video\" when title not provided","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870701,"status":"passed","title":"uses default title \"Mission Video\" when title not provided"},{"ancestorTitles":["VideoPlayer Component","YouTube Embed"],"duration":6,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component YouTube Embed renders YouTube iframe with video ID from youtube.com URL","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870703,"status":"passed","title":"renders YouTube iframe with video ID from youtube.com URL"},{"ancestorTitles":["VideoPlayer Component","YouTube Embed"],"duration":4,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component YouTube Embed extracts video ID from youtu.be URL","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870709,"status":"passed","title":"extracts video ID from youtu.be URL"},{"ancestorTitles":["VideoPlayer Component","YouTube Embed"],"duration":4,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component YouTube Embed displays link to open in YouTube","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870713,"status":"passed","title":"displays link to open in YouTube"},{"ancestorTitles":["VideoPlayer Component","YouTube Embed"],"duration":5,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component YouTube Embed prefers YouTube embed over direct video when both provided","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870717,"status":"passed","title":"prefers YouTube embed over direct video when both provided"},{"ancestorTitles":["VideoPlayer Component","YouTube Embed"],"duration":5,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component YouTube Embed extracts video ID from embed URL","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870722,"status":"passed","title":"extracts video ID from embed URL"},{"ancestorTitles":["VideoPlayer Component","Component Props"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Component Props applies custom className to container","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870727,"status":"passed","title":"applies custom className to container"},{"ancestorTitles":["VideoPlayer Component","Component Props"],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Component Props accepts and displays custom title prop","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870728,"status":"passed","title":"accepts and displays custom title prop"},{"ancestorTitles":["VideoPlayer Component","Component Props"],"duration":5,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Component Props respects showDownload prop","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870730,"status":"passed","title":"respects showDownload prop"},{"ancestorTitles":["VideoPlayer Component","Component Structure"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Component Structure video element has controls and preload metadata","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870735,"status":"passed","title":"video element has controls and preload metadata"},{"ancestorTitles":["VideoPlayer Component","Component Structure"],"duration":4,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"VideoPlayer Component Component Structure YouTube iframe has allowFullScreen attribute","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870736,"status":"passed","title":"YouTube iframe has allowFullScreen attribute"}],"endTime":1779058870741,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/video-player.test.tsx","startTime":1779058870586,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["FirestoreMissionRepository"],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"FirestoreMissionRepository omits undefined fields before writing to Firestore","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1779058870830,"status":"passed","title":"omits undefined fields before writing to Firestore"},{"ancestorTitles":["FirestoreMissionRepository"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"FirestoreMissionRepository strips comments from mission code before writing to Firestore","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870832,"status":"passed","title":"strips comments from mission code before writing to Firestore"}],"endTime":1779058870833,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/FirestoreMissionRepository.test.ts","startTime":1779058870744,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["rover movement scaffold"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"rover movement scaffold Task 31: rover movement calculations are correct","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058870858,"status":"todo","title":"Task 31: rover movement calculations are correct"}],"endTime":1779058870859,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/rover-movement.test.ts","startTime":1779058870835,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["initializeFirebaseAdmin"],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"initializeFirebaseAdmin falls back to REACT_APP_FIREBASE_PROJECT_ID when FIREBASE_PROJECT_ID is unset","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870883,"status":"passed","title":"falls back to REACT_APP_FIREBASE_PROJECT_ID when FIREBASE_PROJECT_ID is unset"},{"ancestorTitles":["initializeFirebaseAdmin"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"initializeFirebaseAdmin trims whitespace and surrounding quotes from Firebase Admin env values","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870885,"status":"passed","title":"trims whitespace and surrounding quotes from Firebase Admin env values"},{"ancestorTitles":["initializeFirebaseAdmin"],"duration":5,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"initializeFirebaseAdmin throws a clear error when Firebase Admin credentials are missing","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870885,"status":"passed","title":"throws a clear error when Firebase Admin credentials are missing"}],"endTime":1779058870890,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/firebase-admin.test.ts","startTime":1779058870861,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["allowlist enforcement","approved rover commands"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement approved rover commands allows approved rover movement commands","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870912,"status":"passed","title":"allows approved rover movement commands"},{"ancestorTitles":["allowlist enforcement","approved rover commands"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement approved rover commands allows approved rover utility commands","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870913,"status":"passed","title":"allows approved rover utility commands"},{"ancestorTitles":["allowlist enforcement","approved rover commands"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement approved rover commands allows safe Python built-ins","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870913,"status":"passed","title":"allows safe Python built-ins"},{"ancestorTitles":["allowlist enforcement","disallowed imports"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement disallowed imports blocks os module import","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1779058870913,"status":"passed","title":"blocks os module import"},{"ancestorTitles":["allowlist enforcement","disallowed imports"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement disallowed imports blocks subprocess module import","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1779058870914,"status":"passed","title":"blocks subprocess module import"},{"ancestorTitles":["allowlist enforcement","disallowed imports"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement disallowed imports blocks from ... import pattern","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870914,"status":"passed","title":"blocks from ... import pattern"},{"ancestorTitles":["allowlist enforcement","disallowed imports"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement disallowed imports blocks socket module for network access","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870914,"status":"passed","title":"blocks socket module for network access"},{"ancestorTitles":["allowlist enforcement","disallowed imports"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement disallowed imports blocks sys module","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870914,"status":"passed","title":"blocks sys module"},{"ancestorTitles":["allowlist enforcement","non-rover function calls"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement non-rover function calls blocks non-existent rover commands","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1779058870915,"status":"passed","title":"blocks non-existent rover commands"},{"ancestorTitles":["allowlist enforcement","non-rover function calls"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement non-rover function calls reports all violations in code","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870915,"status":"passed","title":"reports all violations in code"},{"ancestorTitles":["allowlist enforcement","dangerous built-ins"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement dangerous built-ins blocks eval() built-in","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870915,"status":"passed","title":"blocks eval() built-in"},{"ancestorTitles":["allowlist enforcement","dangerous built-ins"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement dangerous built-ins blocks exec() built-in","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870916,"status":"passed","title":"blocks exec() built-in"},{"ancestorTitles":["allowlist enforcement","dangerous built-ins"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement dangerous built-ins blocks open() built-in for file access","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870916,"status":"passed","title":"blocks open() built-in for file access"},{"ancestorTitles":["allowlist enforcement","dangerous built-ins"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement dangerous built-ins blocks __import__() for dynamic imports","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870917,"status":"passed","title":"blocks __import__() for dynamic imports"},{"ancestorTitles":["allowlist enforcement","error message formatting"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement error message formatting includes line numbers in error messages","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870917,"status":"passed","title":"includes line numbers in error messages"},{"ancestorTitles":["allowlist enforcement","error message formatting"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement error message formatting formats error messages for display to learners","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870917,"status":"passed","title":"formats error messages for display to learners"},{"ancestorTitles":["allowlist enforcement","error message formatting"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement error message formatting returns success message for valid code","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870917,"status":"passed","title":"returns success message for valid code"},{"ancestorTitles":["allowlist enforcement","AST analyzer direct tests"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement AST analyzer direct tests AST analyzer detects disallowed imports","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870917,"status":"passed","title":"AST analyzer detects disallowed imports"},{"ancestorTitles":["allowlist enforcement","AST analyzer direct tests"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement AST analyzer direct tests AST analyzer detects non-rover function calls","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870918,"status":"passed","title":"AST analyzer detects non-rover function calls"},{"ancestorTitles":["allowlist enforcement","AST analyzer direct tests"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"allowlist enforcement AST analyzer direct tests AST analyzer returns empty array for safe code","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870918,"status":"passed","title":"AST analyzer returns empty array for safe code"}],"endTime":1779058870918,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/allowlist.test.ts","startTime":1779058870892,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["Learner Entity","createAnonymousLearner"],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity createAnonymousLearner creates a learner with valid defaults","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1779058870942,"status":"passed","title":"creates a learner with valid defaults"},{"ancestorTitles":["Learner Entity","createAnonymousLearner"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity createAnonymousLearner assigns a random avatar color","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870944,"status":"passed","title":"assigns a random avatar color"},{"ancestorTitles":["Learner Entity","createAnonymousLearner"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity createAnonymousLearner sets timestamps correctly","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870945,"status":"passed","title":"sets timestamps correctly"},{"ancestorTitles":["Learner Entity","createAnonymousLearner"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity createAnonymousLearner initializes device array with first device","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870945,"status":"passed","title":"initializes device array with first device"},{"ancestorTitles":["Learner Entity","isActiveLearner"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity isActiveLearner returns false for new learner with no missions","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870945,"status":"passed","title":"returns false for new learner with no missions"},{"ancestorTitles":["Learner Entity","isActiveLearner"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity isActiveLearner returns true for learner with missions","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870945,"status":"passed","title":"returns true for learner with missions"},{"ancestorTitles":["Learner Entity","isActiveLearner"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity isActiveLearner returns true even with zero completions","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870945,"status":"passed","title":"returns true even with zero completions"},{"ancestorTitles":["Learner Entity","sanitizeDisplayName"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity sanitizeDisplayName removes email addresses","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870946,"status":"passed","title":"removes email addresses"},{"ancestorTitles":["Learner Entity","sanitizeDisplayName"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity sanitizeDisplayName trims whitespace","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870946,"status":"passed","title":"trims whitespace"},{"ancestorTitles":["Learner Entity","sanitizeDisplayName"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity sanitizeDisplayName limits length to 20 characters","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870946,"status":"passed","title":"limits length to 20 characters"},{"ancestorTitles":["Learner Entity","sanitizeDisplayName"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity sanitizeDisplayName handles multiple emails","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870946,"status":"passed","title":"handles multiple emails"},{"ancestorTitles":["Learner Entity","sanitizeDisplayName"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity sanitizeDisplayName preserves valid nicknames","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1779058870946,"status":"passed","title":"preserves valid nicknames"},{"ancestorTitles":["Learner Entity","sanitizeDisplayName"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity sanitizeDisplayName handles empty string","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870946,"status":"passed","title":"handles empty string"},{"ancestorTitles":["Learner Entity","sanitizeDisplayName"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity sanitizeDisplayName handles only whitespace","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870947,"status":"passed","title":"handles only whitespace"},{"ancestorTitles":["Learner Entity","Learner statistics"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity Learner statistics calculates success rate correctly","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870947,"status":"passed","title":"calculates success rate correctly"},{"ancestorTitles":["Learner Entity","Learner statistics"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity Learner statistics handles zero missions for success rate","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870947,"status":"passed","title":"handles zero missions for success rate"},{"ancestorTitles":["Learner Entity","Learner statistics"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity Learner statistics handles perfect success rate","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870947,"status":"passed","title":"handles perfect success rate"},{"ancestorTitles":["Learner Entity","Multi-device support"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity Multi-device support can track multiple devices","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870947,"status":"passed","title":"can track multiple devices"},{"ancestorTitles":["Learner Entity","Multi-device support"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Learner Entity Multi-device support maintains device history","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870947,"status":"passed","title":"maintains device history"}],"endTime":1779058870948,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/Learner.test.ts","startTime":1779058870920,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["Video Display Integration","Mission Entity Video Fields"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Mission Entity Video Fields mission entity supports videoUrl field","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870975,"status":"passed","title":"mission entity supports videoUrl field"},{"ancestorTitles":["Video Display Integration","Mission Entity Video Fields"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Mission Entity Video Fields mission entity supports youtubeUrl field","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870975,"status":"passed","title":"mission entity supports youtubeUrl field"},{"ancestorTitles":["Video Display Integration","Mission Entity Video Fields"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Mission Entity Video Fields mission entity supports both video fields simultaneously","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870976,"status":"passed","title":"mission entity supports both video fields simultaneously"},{"ancestorTitles":["Video Display Integration","Mission Entity Video Fields"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Mission Entity Video Fields video fields are optional in mission entity","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870976,"status":"passed","title":"video fields are optional in mission entity"},{"ancestorTitles":["Video Display Integration","Video URL Validation"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Video URL Validation accepts valid GCS URL format","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1779058870976,"status":"passed","title":"accepts valid GCS URL format"},{"ancestorTitles":["Video Display Integration","Video URL Validation"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Video URL Validation accepts valid YouTube URL format","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870976,"status":"passed","title":"accepts valid YouTube URL format"},{"ancestorTitles":["Video Display Integration","Video URL Validation"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Video URL Validation accepts valid youtu.be URL format","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870976,"status":"passed","title":"accepts valid youtu.be URL format"},{"ancestorTitles":["Video Display Integration","Completed Mission Video Workflow"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Completed Mission Video Workflow completed mission includes video URL","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870976,"status":"passed","title":"completed mission includes video URL"},{"ancestorTitles":["Video Display Integration","Completed Mission Video Workflow"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Completed Mission Video Workflow failed mission includes video URL showing failure","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870977,"status":"passed","title":"failed mission includes video URL showing failure"},{"ancestorTitles":["Video Display Integration","Completed Mission Video Workflow"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Completed Mission Video Workflow queued mission does not have video URL","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870977,"status":"passed","title":"queued mission does not have video URL"},{"ancestorTitles":["Video Display Integration","Completed Mission Video Workflow"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Completed Mission Video Workflow processing mission does not have video URL","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058870977,"status":"passed","title":"processing mission does not have video URL"},{"ancestorTitles":["Video Display Integration","Video File Types"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Video File Types supports MP4 video format","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870977,"status":"passed","title":"supports MP4 video format"},{"ancestorTitles":["Video Display Integration","Video File Types"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Video Display Integration Video File Types supports WebM video format","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1779058870977,"status":"passed","title":"supports WebM video format"}],"endTime":1779058870978,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/video-display.test.ts","startTime":1779058870950,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["queue confirmation"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"queue confirmation confirmation response includes queue position and estimated wait","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1779058871001,"status":"passed","title":"confirmation response includes queue position and estimated wait"},{"ancestorTitles":["queue confirmation"],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"queue confirmation queue position 1 has zero estimated wait","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058871001,"status":"passed","title":"queue position 1 has zero estimated wait"},{"ancestorTitles":["queue confirmation"],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"queue confirmation handles empty queue correctly","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1779058871001,"status":"passed","title":"handles empty queue correctly"}],"endTime":1779058871002,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/confirmation-response.test.ts","startTime":1779058870980,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["execution timeout scaffold"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"execution timeout scaffold Task 75: execution timeout fires correctly","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058871023,"status":"todo","title":"Task 75: execution timeout fires correctly"}],"endTime":1779058871025,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/execution-timeout.test.ts","startTime":1779058871004,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["sandbox disallowed commands scaffold"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"sandbox disallowed commands scaffold Task 76: disallowed commands raise exceptions in the sandbox","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058871046,"status":"todo","title":"Task 76: disallowed commands raise exceptions in the sandbox"}],"endTime":1779058871046,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/sandbox-disallowed-commands.test.ts","startTime":1779058871027,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["completion notification scaffold"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"completion notification scaffold Task 61: completion notification fires after the mission status update","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058871068,"status":"todo","title":"Task 61: completion notification fires after the mission status update"}],"endTime":1779058871068,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/completion-notification.test.ts","startTime":1779058871049,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["heartbeat scaffold"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"heartbeat scaffold Task 68: heartbeat detects disconnection","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058871089,"status":"todo","title":"Task 68: heartbeat detects disconnection"}],"endTime":1779058871090,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/heartbeat.test.ts","startTime":1779058871070,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["editor scaffold"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"editor scaffold Task 20: editor renders and accepts learner input","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058871112,"status":"todo","title":"Task 20: editor renders and accepts learner input"}],"endTime":1779058871113,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/unit/editor.test.tsx","startTime":1779058871092,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["websocket delivery scaffold"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"websocket delivery scaffold Task 69: mission payload is delivered over the WebSocket connection","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058871135,"status":"todo","title":"Task 69: mission payload is delivered over the WebSocket connection"}],"endTime":1779058871135,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/websocket-delivery.test.ts","startTime":1779058871116,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["simulator execution scaffold"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"simulator execution scaffold Task 32: submitted code updates simulator state through the execution engine","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058871156,"status":"todo","title":"Task 32: submitted code updates simulator state through the execution engine"}],"endTime":1779058871156,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/simulator-execution.test.ts","startTime":1779058871137,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["queue order integration scaffold"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"queue order integration scaffold Task 47: submitted missions appear in the correct queue order","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1779058871180,"status":"todo","title":"Task 47: submitted missions appear in the correct queue order"}],"endTime":1779058871181,"message":"","name":"/Users/hlali/Documents/mars-rover-mission-authoring/src/__tests__/integration/queue-order.test.ts","startTime":1779058871159,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/mission-authoring/jest.config.ts b/mission-authoring/jest.config.ts new file mode 100644 index 0000000..405cefe --- /dev/null +++ b/mission-authoring/jest.config.ts @@ -0,0 +1,34 @@ +import type { Config } from 'jest'; + +const config: Config = { + preset: 'ts-jest', + testEnvironment: 'node', + rootDir: '.', + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + // nanoid v5 is ESM-only; map to a CJS-compatible mock for ts-jest + '^nanoid$': '/test-utils/nanoidMock.ts', + }, + testMatch: [ + '**/__tests__/**/*.test.ts', + '**/__tests__/**/*.test.tsx', + ], + setupFilesAfterEnv: ['/jest.setup.ts'], + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/**/*.stories.tsx', + '!src/app/**/*.tsx', + ], + transform: { + '^.+\\.tsx?$': ['ts-jest', { + tsconfig: { + jsx: 'react', + esModuleInterop: true, + allowSyntheticDefaultImports: true, + }, + }], + }, +}; + +export default config; diff --git a/mission-authoring/jest.setup.ts b/mission-authoring/jest.setup.ts new file mode 100644 index 0000000..56f50be --- /dev/null +++ b/mission-authoring/jest.setup.ts @@ -0,0 +1,11 @@ +/** + * Jest Setup File + * Runs before all tests + */ + +import '@testing-library/jest-dom'; + +// Mock environment variables for tests +process.env.FIREBASE_PROJECT_ID = 'test-project'; +process.env.FIREBASE_CLIENT_EMAIL = 'test@test.iam.gserviceaccount.com'; +process.env.FIREBASE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----\n'; diff --git a/mission-authoring/next.config.ts b/mission-authoring/next.config.ts new file mode 100644 index 0000000..d9ce606 --- /dev/null +++ b/mission-authoring/next.config.ts @@ -0,0 +1,28 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ + // Pin the workspace root to this app dir. Without it, the root-level + // package-lock.json (dev tooling) makes Next infer the wrong root, breaking + // the @/* alias when deps are only installed in this app (e.g. CI). + turbopack: { + root: process.cwd(), + }, + experimental: { + viewTransition: true, + }, + images: { + qualities: [100, 75], + remotePatterns: [ + { + protocol: 'https', + hostname: 'img.youtube.com', + pathname: '/vi/**', + }, + ], + }, + reactCompiler: true, + serverExternalPackages: ["firebase-admin"], +}; + +export default nextConfig; diff --git a/mission-authoring/package-lock.json b/mission-authoring/package-lock.json new file mode 100644 index 0000000..1ae6645 --- /dev/null +++ b/mission-authoring/package-lock.json @@ -0,0 +1,13439 @@ +{ + "name": "mars-rover-mission-authoring", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mars-rover-mission-authoring", + "version": "0.1.0", + "dependencies": { + "@monaco-editor/react": "^4.7.0", + "blockly": "^12.5.1", + "firebase": "^12.12.0", + "firebase-admin": "^13.10.0", + "lucide-react": "^1.17.0", + "nanoid": "^5.1.9", + "next": "16.2.4", + "react": "19.2.4", + "react-dom": "19.2.4", + "zod": "^3.25.76" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/jest": "^30.0.0", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "babel-plugin-react-compiler": "1.0.0", + "concurrently": "^9.1.2", + "eslint": "^9", + "eslint-config-next": "16.2.4", + "jest": "^30.3.0", + "jest-environment-jsdom": "^30.3.0", + "tailwindcss": "^4", + "ts-jest": "^29.4.9", + "ts-node": "^10.9.2", + "typescript": "^5" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz", + "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==", + "license": "MIT" + }, + "node_modules/@firebase/ai": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.12.0.tgz", + "integrity": "sha512-b+OL4vdyiSLZL/7dLd67V55CjKJvU9MpNmwnday7eA6GG2+J4iwUEsEHgw0/jKY3A41FfkF0SrnYFvtKbQZ65A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.4", + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.22", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.22.tgz", + "integrity": "sha512-8BSaq/QRGU1+xyi8L2PTLTJU7MH9aMA72RQdIxrbhWFauOZY9OXo8f2YDN/972xA8d588tlnNVEQ2Mo69pT9Ow==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/installations": "0.6.22", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.28", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.28.tgz", + "integrity": "sha512-lIAlqUUbBu93FJMlQfslryQtBwwzdzvp23ePC6FNgymXk6Ook5v4Uvc0vdutvoIeqmyA3LfP0ZeRFK8+11kOOQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.22", + "@firebase/analytics-types": "0.8.4", + "@firebase/component": "0.7.3", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.4.tgz", + "integrity": "sha512-zQ+XTgkwH6CY/eUSHJRP7e4LxM30RCxlCmob5sy2axs25GE3Ny0XdgpDscMTHHQIGqWkxPXad4w2Mw9sCgT8zQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app": { + "version": "0.14.12", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.12.tgz", + "integrity": "sha512-FT+HoNp1NdaZ/N26hCwV3WbxS1m6gTn3p2QRBQ3KH7YqyCQqJx0iT7126RgVk68/Rq+9DeL/zCFnHZ0C4u1nLQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.3.tgz", + "integrity": "sha512-aJ4DfubWfTO8/2vhEhIAizOoOmiycESTU32e+OUgbWcS/G3PA4Vxlr/9zaiN2wfUG2AptQ7DTvj00tyuFZP5Bg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.3.tgz", + "integrity": "sha512-L3AKIRTJxT9b7cDUH3OyV8gWTnmW3vYkwdzRsukWt4kbPBTct12xalnyvHDkm1lKkr+cQq/4uzBx1bOWsQ2ciw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check": "0.11.3", + "@firebase/app-check-types": "0.5.4", + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.4.tgz", + "integrity": "sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.4.tgz", + "integrity": "sha512-xV7JsIyzVr15aA7f3Pi0rB9gdBuVubs89FGA8VkRYA4g0l78poADgdfrScgf7NndSg9mm7cR7PJyY0+t22KaGw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-compat": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.12.tgz", + "integrity": "sha512-Pe513OBerK/CIBxz4/za9atd5MsZtd6DzHz4cmqkvkrcDWhQChAoHBpZ3McuZNuSP8YZiKwfX/J1frR07l15/w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app": "0.14.12", + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.5.tgz", + "integrity": "sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/logger": "0.5.1" + } + }, + "node_modules/@firebase/auth": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.13.1.tgz", + "integrity": "sha512-/1nkKY/MicI+I9WWcx6R4NKs77AaW9NQ0IwsFdUBomWrW0/cXEmopfM2dtLm2oI1qG6z6vom3CXZDHJIJXoMuw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^2.2.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.6.tgz", + "integrity": "sha512-KDJ/GAf/rt7galOpn3DRb2buFfGkZCsHTryKjXDG0eeRnok4+2B4nnkMOMdjRnPkElmcJv2Ao0vEA6kp5m98PQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth": "1.13.1", + "@firebase/auth-types": "0.13.1", + "@firebase/component": "0.7.3", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.5.tgz", + "integrity": "sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-types": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.1.tgz", + "integrity": "sha512-0c1Mnid0uMDfGJHeUS4zfvBa4/CedJXotGy/n/NZJnBjwiJawt0ZYU+wH2VAVLiRCEfG2ncCkAX3yd1/2nrB7g==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.3.tgz", + "integrity": "sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.7.0.tgz", + "integrity": "sha512-ar9sNOJh5poQCSMSVlnVE8eo8+usTD1POWDCv65omkKUvnFMcdXaQ7J/e7WGKqJzcEMgiezSX/TZiKHZkItMbQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.5", + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.3.tgz", + "integrity": "sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.4", + "@firebase/auth-interop-types": "0.2.5", + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.4.tgz", + "integrity": "sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/database": "1.1.3", + "@firebase/database-types": "1.0.20", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.20.tgz", + "integrity": "sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.5", + "@firebase/util": "1.15.1" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.14.1.tgz", + "integrity": "sha512-PouS0NJZ3NYOZE/tPDvXa8VUeJ10Ll//7jIdFvMYdhQkd/P3O7nlqhyoTmY0h8Xa9hxg+H0j6gxUytJcoZ9YOg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "@firebase/webchannel-wrapper": "1.0.6", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.9.tgz", + "integrity": "sha512-NPtBuFr79BbIQJXFWhW4xFC6rBksK8/ewqCTYbbAYfZBDDx0/iHTUj4WpKi5D4d0Pn2Md/3T/e5V9379G5N/Zg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/firestore": "4.14.1", + "@firebase/firestore-types": "3.0.4", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.4.tgz", + "integrity": "sha512-jGn+JSS4X9zZsrfu7Yw66v5YRdOLD1oyQh4USR0xWl4CUqV/DA6bNIXRPpxH/cUl3iVTNiP6MN7g+EL42A4qfA==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.4.tgz", + "integrity": "sha512-oB5rpm2Emxn2+IS1gRelAeT/5tSZMwM/KhqC5LnJsmTNnS1ZDhD7ZMZNgCI8vchTW6PbaXIwEnpUryGuIQsNbg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.4", + "@firebase/auth-interop-types": "0.2.5", + "@firebase/component": "0.7.3", + "@firebase/messaging-interop-types": "0.2.4", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.4.tgz", + "integrity": "sha512-Be+MwhseVf/eFAZwGrFJGok6S7cmsLrAPK8MgyM8LjM0MewTsx2n01WOOca9jio1UsCZOJ0aVyQobnINcdNuIQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/functions": "0.13.4", + "@firebase/functions-types": "0.6.4", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.4.tgz", + "integrity": "sha512-zV6kgqtduR4rUAdC/ilS7kmb93XD7bEZoJDlVBZqlOw2uGGGCNBQBuleww2rr0Ulr3L9o2TDjumEt68/l1f9DQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/installations": { + "version": "0.6.22", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.22.tgz", + "integrity": "sha512-ef6nn3GGQTdReCfotRMG77PJZu8CqEbiK5pEoBnM0gTu/Z9v0i/az2p3HABsa/1beQmmyh1OsOjf7P5+pgwdZw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/util": "1.15.1", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.22", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.22.tgz", + "integrity": "sha512-C/zpAuTP5S9OgKSPvXRupw3hoY/JZSlA1wFjD/Sb7LIQE0FNbcMdO8Y4KXVEkjVzma/DDDDIAzxEXqKMAzc88w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/installations": "0.6.22", + "@firebase/installations-types": "0.5.4", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.4.tgz", + "integrity": "sha512-U2eFapdHwjb43Vx9o+Pmj4dFfvcHEK1IirEFLqMtWrTHvmdrS3gBpBD1kmJk/9HjsOtoHZxJ2Paoe79e+L1ZPg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.1.tgz", + "integrity": "sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.26", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.26.tgz", + "integrity": "sha512-lHVTO9uLofymHVWkYeUtMddIPcmJvSzVbHRB88W6XKfxbcKF+p3QrfqKhDxremSB4NQjUla1Gwn7d9umSMmt/w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/installations": "0.6.22", + "@firebase/messaging-interop-types": "0.2.4", + "@firebase/util": "1.15.1", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.26", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.26.tgz", + "integrity": "sha512-fn0XvWOfK4tsDLSipwJUW9Cp6ahWA6z+iJHxZ0pHp9MzMSUNQx85yuxZAuI7gkGXfqs7+DqEDHyyS7jDGswrmQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/messaging": "0.12.26", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.4.tgz", + "integrity": "sha512-wrzITQq+xw5LtygX7O0fu43/k9ABQ4x5H9/sR5m1SbNnhIRI5xd3+raSNJaJkYC4BUhM9A4ZNSnyR2sjhxnb2Q==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/performance": { + "version": "0.7.12", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.12.tgz", + "integrity": "sha512-fe7nV8teUU3OBHlMUZ9Lw4gLhCW2k4m5Uc3pfWGV+fl8uwJQBGp9Q3lqsJ+HSrFu3Q2pJyLAgrClPGSKyDeYgQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/installations": "0.6.22", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.25.tgz", + "integrity": "sha512-q6NjTXpIPoFuUmCmMN/maCdTgzT6aExs9xZo+PxfVLj6uLVGvpyAD6XWjmcrb7jChsFBYbq7E5dyNDF7Zhy9kA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/performance": "0.7.12", + "@firebase/performance-types": "0.2.4", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.4.tgz", + "integrity": "sha512-kJSEk7b0uhpcPRyL4SQ/GPujLqk52XNKcXlnsKDbWGAb9vugcLvOU3u6zfEdwd+d8hWJb5S5ZizV1JFFI0nkKg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/remote-config": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.3.tgz", + "integrity": "sha512-ggGKAaLy9YNOvpFoQZgm5p5SiFw3ZFtwti08dojnBQmQicpThTxvG5xZMSpCTYMj2o3gM/yK9CVd2w+kZub8YA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/installations": "0.6.22", + "@firebase/logger": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.24", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.24.tgz", + "integrity": "sha512-EWZTt6fJ7YmPHodQNsSxAIDZY2x8P5kRPvXAc5CmzzBm+NyPFhODbfDsNllDXDL8jlzp50bVWjDY+BXepZS9Mg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/logger": "0.5.1", + "@firebase/remote-config": "0.8.3", + "@firebase/remote-config-types": "0.5.1", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.1.tgz", + "integrity": "sha512-cX/1LT6KQwkXzck2eSzeKnuvXZCyr8qaPpDcikoJs7jmI+oBOXixpDLeDtWj1U6GNMkIoXrEDNoyT2Ypcyp5/A==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/storage": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.3.tgz", + "integrity": "sha512-YX4/YL6P6/fufSSeGnVhjWddcIXbFq2cWIhMKFTZo1E/Rtcl2mJj/BYUQTwJfcE1Tl8un1FOya4L05jcSLN/Eg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.3.tgz", + "integrity": "sha512-gruVqjtUGX8tEoeNbaWXZm0Zfcfcb7fvmDmBxV8yPAbWvExRnZYLO2+qw9idxNE7BvPXt5csyjSYHy//dAizxw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.3", + "@firebase/storage": "0.14.3", + "@firebase/storage-types": "0.8.4", + "@firebase/util": "1.15.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.4.tgz", + "integrity": "sha512-BT7cwxJOx8SWwlQfrlC+bD/Sk3Cw+1odCi8UZNFNWTVZoPsBnA5W+mqtZzVnvsdJpXCFGSGQ7R7vOR6dtM/BRA==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.1.tgz", + "integrity": "sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.6.tgz", + "integrity": "sha512-Vr/Mqu79dMwGRAyGbJ4uN4+BtXB3/mRTdzetD1daWNeG8QaWuzhhbG77GltO5c0yYmYls8i250iX73624GJd7Q==", + "license": "Apache-2.0" + }, + "node_modules/@google-cloud/firestore": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.6.tgz", + "integrity": "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@opentelemetry/api": "^1.3.0", + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.3", + "protobufjs": "^7.2.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz", + "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "<4.1.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^5.3.4", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", + "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", + "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.4.1", + "@jest/pattern": "30.4.0", + "@jest/reporters": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.4.1", + "jest-config": "30.4.2", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-resolve-dependencies": "30.4.2", + "jest-runner": "30.4.2", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "jest-watcher": "30.4.1", + "pretty-format": "30.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-mock": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.4.1.tgz", + "integrity": "sha512-dSlKrqug3siYNHVnjwIldShY12wAH3spwRltO/+8VOjg0X+xEq7vOs3DbBs4LRKsu7OH+NUb9kuZUNBF9Ho3TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/jsdom": "^21.1.7", + "@types/node": "*", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/@jest/expect": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.4.1", + "jest-snapshot": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", + "@types/node": "*", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", + "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/types": "30.4.1", + "jest-mock": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", + "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", + "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", + "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.4.1", + "@jest/types": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", + "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.4.1", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", + "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.4.1", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@monaco-editor/loader": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", + "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz", + "integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.4.tgz", + "integrity": "sha512-tOX826JJ96gYK/go18sPUgMq9FK1tqxBFfUCEufJb5XIkWFFmpgU7mahJANKGkHs7F41ir3tReJ3Lv5La0RvhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz", + "integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", + "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", + "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", + "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", + "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", + "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", + "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", + "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", + "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", + "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "postcss": "^8.5.10", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.40.tgz", + "integrity": "sha512-xxx6M2IpSTnnKcR0cMvIiohkiCx20/oRPtWGbenFygKCGl3zqUzdNjQ/1V4solq1LU+dgv0nQzeGOuqkqZGg0Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/request": { + "version": "2.48.13", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", + "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.5" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", + "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/type-utils": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz", + "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", + "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.2", + "@typescript-eslint/types": "^8.59.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", + "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", + "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", + "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz", + "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", + "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.2", + "@typescript-eslint/tsconfig-utils": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz", + "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", + "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT", + "optional": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz", + "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-jest": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", + "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.4.1", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.4.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", + "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-plugin-react-compiler": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", + "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.4.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/blockly": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-12.5.1.tgz", + "integrity": "sha512-etXLpUtEkcRibHGwIJ4BsvnIzMJJs0C0yPIjE/W0NCtj8ACha/a7Q9n7Ib6+j7N4EzQ0p28YPZMnypi5pNIj1g==", + "license": "Apache-2.0", + "dependencies": { + "jsdom": "26.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.353", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", + "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.2.tgz", + "integrity": "sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.4.tgz", + "integrity": "sha512-A6ekXYFj/YQxBPMl45g3e+U8zJo+X2+ZQwcz34pPKjpc/3S4roBA2Rd9xWB4FKuSxhofo1/95WjzmUY+wHrOhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.2.4", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/farmhash-modern": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", + "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.8.0.tgz", + "integrity": "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.2.0", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.3.0", + "xml-naming": "^0.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/firebase": { + "version": "12.13.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.13.0.tgz", + "integrity": "sha512-iutR8ejvAqk6qUClnsPz3U3VIjTWp243AX4cD3iifak5t56to1J29xUIQgSDDzaAqKvhshZerzSahwMQj2TlvA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/ai": "2.12.0", + "@firebase/analytics": "0.10.22", + "@firebase/analytics-compat": "0.2.28", + "@firebase/app": "0.14.12", + "@firebase/app-check": "0.11.3", + "@firebase/app-check-compat": "0.4.3", + "@firebase/app-compat": "0.5.12", + "@firebase/app-types": "0.9.5", + "@firebase/auth": "1.13.1", + "@firebase/auth-compat": "0.6.6", + "@firebase/data-connect": "0.7.0", + "@firebase/database": "1.1.3", + "@firebase/database-compat": "2.1.4", + "@firebase/firestore": "4.14.1", + "@firebase/firestore-compat": "0.4.9", + "@firebase/functions": "0.13.4", + "@firebase/functions-compat": "0.4.4", + "@firebase/installations": "0.6.22", + "@firebase/installations-compat": "0.2.22", + "@firebase/messaging": "0.12.26", + "@firebase/messaging-compat": "0.2.26", + "@firebase/performance": "0.7.12", + "@firebase/performance-compat": "0.2.25", + "@firebase/remote-config": "0.8.3", + "@firebase/remote-config-compat": "0.2.24", + "@firebase/storage": "0.14.3", + "@firebase/storage-compat": "0.4.3", + "@firebase/util": "1.15.1" + } + }, + "node_modules/firebase-admin": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.10.0.tgz", + "integrity": "sha512-rbuCrJvYRwqBqvbccMS8fj/x2zsaMisdf5RQbRzQzr14Rbq9r2UlpuBHqWAwrO6c9dIRF56xF/xoepXsD5yDuQ==", + "license": "Apache-2.0", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@firebase/database-compat": "^2.0.0", + "@firebase/database-types": "^1.0.6", + "farmhash-modern": "^1.1.0", + "fast-deep-equal": "^3.1.1", + "google-auth-library": "^10.6.1", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^7.11.0", + "@google-cloud/storage": "^7.19.0" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "license": "MIT", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT", + "optional": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library/node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/google-gax": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", + "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/google-gax/node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/google-gax/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "optional": true, + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", + "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.4.2", + "@jest/types": "30.4.1", + "import-local": "^3.2.0", + "jest-cli": "30.4.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", + "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.4.1", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", + "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "p-limit": "^3.1.0", + "pretty-format": "30.4.1", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", + "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.4.2", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", + "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.4.0", + "@jest/test-sequencer": "30.4.1", + "@jest/types": "30.4.1", + "babel-jest": "30.4.1", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.4.2", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-runner": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "parse-json": "^5.2.0", + "pretty-format": "30.4.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-diff": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.4.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", + "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", + "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "jest-util": "30.4.1", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.4.1.tgz", + "integrity": "sha512-o3nfaN4zej7qgk2X0j8Jhq/S9nAVKs2xK3QeQxeHVvpkEPxaA1yxDGydR+iVI7zPy7Cp62Aq2h3Ja46QvfWHGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/environment-jsdom-abstract": "30.4.1", + "jsdom": "^26.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", + "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-mock": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", + "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", + "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.4.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", + "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", + "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.4.0", + "jest-snapshot": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", + "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.4.1", + "@jest/environment": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-haste-map": "30.4.1", + "jest-leak-detector": "30.4.1", + "jest-message-util": "30.4.1", + "jest-resolve": "30.4.1", + "jest-runtime": "30.4.2", + "jest-util": "30.4.1", + "jest-watcher": "30.4.1", + "jest-worker": "30.4.1", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", + "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/globals": "30.4.1", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", + "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.4.1", + "graceful-fs": "^4.2.11", + "jest-diff": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "pretty-format": "30.4.1", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", + "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.4.1", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", + "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.4.1", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", + "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.4.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.2.tgz", + "integrity": "sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/lucide-react": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", + "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "license": "MIT", + "peer": true, + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz", + "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz", + "integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==", + "license": "MIT", + "dependencies": { + "@next/env": "16.2.4", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.4", + "@next/swc-darwin-x64": "16.2.4", + "@next/swc-linux-arm64-gnu": "16.2.4", + "@next/swc-linux-arm64-musl": "16.2.4", + "@next/swc-linux-x64-gnu": "16.2.4", + "@next/swc-linux-x64-musl": "16.2.4", + "@next/swc-win32-arm64-msvc": "16.2.4", + "@next/swc-win32-x64-msvc": "16.2.4", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.7.tgz", + "integrity": "sha512-NGnrxS/nLKUo5nkbVQxlC71sB4hdfImdYIbFeSCidxtwATx0AHRPcANSLd0q5Bb2BkoSWo2iisQhGg5/r+ihbA==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.1", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT", + "optional": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT", + "optional": true + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/teeny-request/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.4", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.2.tgz", + "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.2", + "@typescript-eslint/parser": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/mission-authoring/package.json b/mission-authoring/package.json new file mode 100644 index 0000000..485365f --- /dev/null +++ b/mission-authoring/package.json @@ -0,0 +1,46 @@ +{ + "name": "mars-rover-mission-authoring", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" + }, + "dependencies": { + "@monaco-editor/react": "^4.7.0", + "blockly": "^12.5.1", + "firebase": "^12.12.0", + "firebase-admin": "^13.10.0", + "lucide-react": "^1.17.0", + "nanoid": "^5.1.9", + "next": "16.2.4", + "react": "19.2.4", + "react-dom": "19.2.4", + "zod": "^3.25.76" + }, + "description": "Learner-facing mission authoring platform for Mars Rover", + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/jest": "^30.0.0", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "babel-plugin-react-compiler": "1.0.0", + "concurrently": "^9.1.2", + "eslint": "^9", + "eslint-config-next": "16.2.4", + "jest": "^30.3.0", + "jest-environment-jsdom": "^30.3.0", + "tailwindcss": "^4", + "ts-jest": "^29.4.9", + "ts-node": "^10.9.2", + "typescript": "^5" + } +} diff --git a/mission-authoring/postcss.config.mjs b/mission-authoring/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/mission-authoring/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/mission-authoring/public/rover-hero.jpg b/mission-authoring/public/rover-hero.jpg new file mode 100644 index 0000000000000000000000000000000000000000..054a6eb0d033c1b02ca55488ab6f879639761d10 GIT binary patch literal 152870 zcmbTdd0Z1``z}672%CTcg9Zd-Cjp@Z0dBO*aT5k0B1>3| zfH7oY5fzP!Ra-#9rc?+>711Iniu;Dv*0$#v?EC(H=lj<=2R{Qbnas@d%yVD&bzS#8 z|J?p(5?aIviwJ`-7zo0Ef6za7p-@OyTU$pPudAb@qo=2Fv=3=IjMwwAV@ z|JOhNbU?9kJr)yd+!6ELt4go6I*hywkd19j$Ib)nOAl~*FL1J(~Ld*+QEyreZQXm;w1!g z3r8nsBFTl~?X$wy&p&h}Ei9ZK!QjNjCnT;~&E1f)F?CZKKRtWL&YWGl_vGdm6c!yQ zK8Tc8h$^e9YmSPg4Klex+1S*4_MGbcg^TT%e(bu|t?udV>mSgJjNTgi_4b|d2UF7z zAI&^|GW+z++jsBhK79QB(;w)#Fc9v`u)yyx1N$H2G6v(qYH8uLbkK2Ou)Dy9GuFb} zlC@2OSvu>pOzk}Lbm5S)6K$P(_FnAQX1wjg`imVX4;Ht~$+tLOiymS(X}VJ=8GqEj8;8 z!o5yYxkwAc|6Op;?%C+4*toosdsmx8m!@GGhvJPLP42j+PlZ0WPuHxl#Kc^#>HGVd zoUbGWF-9M0)%%a|f7`KNagMfRoi_a<U$mSd~|CGx~gV48?nSR!z<3!9W+tNo( zB9#bgFrTUHbAB`%Efwf9vm$5^X{Njiff!t(1dIG2JUaTvsm}>xWJ^U9MOi^!&H1k<_1&TSNC4A zhN(uSO=ZZwi8s+;YDj6)ygWZYnGz$8Vq-{IG~!g=LG)6<=?%biLqus+sWwqA%{z!5 zoJDi9F+oUK)NVDQNguY~rZzEit0cf3B|`R;NI%qw{3?QxhI0})Z9Y8KqTS)~{ai4z z2M14ZG9RpGEvO~-%j|V@TK&vRAH`A*REVi7U|t;(#l-T!_k~PcRv78e><9$X(Xyz2 z`@mg)OZ4S%?{#$-g(E*znH33fgRaT7C88Sn2N^CaGIAW;s%lupB>U7def^p+%P&(E zm7Sf>qC%|q$mH_rtyjUq;<7r_kPpHoVn|O+H)jTZPz^d8xlGKx3wksx~-7p&BZ$I#lQ! z+%T?v|NVPoJkeY#GVy0)$USKbDv+eb^1i!yV>Jf$IV(-5RQ*F&¥~ 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

mI=B!sJeQo}^OvOGMP9dMaE%H2OKO$J49el_;a> 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�<=K`(j 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 literal 0 HcmV?d00001 diff --git a/mission-authoring/public/rover-icon.svg b/mission-authoring/public/rover-icon.svg new file mode 100644 index 0000000..796cdf8 --- /dev/null +++ b/mission-authoring/public/rover-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/mission-authoring/public/terrain.jpg b/mission-authoring/public/terrain.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ce73f6694eedd6537f1cb3918a5c06fcc3239b8a GIT binary patch literal 154042 zcmb5VcT^Kw)Hh5Gp(ml3&CY+`$QEyd z=i=t(;^IEsxc)rjvf|?B<>TYy^NM^D+62R&IyWFgGDEB`tgVi@ zH(pi4e5b^PO2~At^tZ~FyLzQJ)(yEWtVvZ310Hz}vyZ@Q83*k8N20;4hMr-x`I~I_ zZzY!JjWz8WSY7YVTN+P_2A!BU?yAl8N|Bt|clCc*5is7+g&{+grhHOundfyL7OoewK*BahCV!pZBsSGwI6GSn7lSTA!s2#nLk zmJdhFTVK(!*Dv|^RU`(axH03tRGu`{t z{t>6_BtKO8Q){Jc6}|~x;jp$L-UPDng!4L|11t9qswqgBkCx7i2CD_22K)iZcn_qU zv`?~_tYq~zxf~xvabF~o5+Hu`)Z)rJgd4@dFB2!PR6k36^-R;f1f*Fza^(^VI>=jN zvy_%Vt82q8sIxr$rOxo$Q{L!-^_BBHB0Zu5CR$x3q_jI>WAm__Di~oU!h}qDW)S0Z z%G3%!p=zBNNUzVAQqdLh3*zuh)qW*J*tW@}3_UUn zot13qY?x?Pt*8mYW=(zlj9z~nPk$u#V@Exne^At>v{lp|2Y;lUQg1NUx#A*g?*fdE z_I#^QP{OI+;!M*6Q16F`>cA2SS6IAHb4v%WO3fYZG+)(c!2o?V2-&^JugqA3K8nOM zY4#{0b3TY<+U?;kF^Kh71Zl0;jfM;w#qt;0YvuEe%%~@#2ED@#U9=aYn!R(B?Cp3u zfp6a*UPwCB{=2_hpx~8H#};!6G?B zH;>Hpqh?Ph5E}1ozW5|C{rb(+f!_NZW#K$&-P*72DrkrbjBE5NOE>mi7;tZmC6UHK z+_%p_cR#czPz&i;S}1-fCau0ha)e$n$SLr20RYz^Cw$kHMj^iB)X$i>q zr7$8j+Gs^p1Ljd^3yX5l&}M;RzxO#yb2lWVpw|>{BdW9~6DVHCqzp1W?;XLlp}w7EYpo5`$98zP->Gi^f3 z>RASIzQ~^a!zHjOd(LcwS!O@3tv11Ay;s2it4UIfw{!>9u-q>N$%1?v0^maVY3AMl zak>`nG13SkFJ-pQ1gB@}i)3bY%(sl(x5BIC-Sy?QJs}8){m{}3lFxqQNS6+{t&dHt z^epM^T|vWql@hBN9s=Jh(CSOXkew zS2$DU`^<5pamW7Y^zVoke}C7_WXi;Q!7>6=+&w#t>s<_!6eP^pF^1A&9quObx$34N zI?|tlUCS}L2!GXe$C>N9mjxs+=N`TI`06-}9Z_*qw7 z<8lm$ltwHFtJF_Rsv7b?(@0m+M)v4}OVg^xbxq4*Cqsl?mtd4D#`94#^S&t=%9PgM%=#f1sb=~DM@P~$h z@^KgXQ>T!sFfKPIY!pT}UfDz5%*}QyTojt4p>K*#yD9<>)PeNgim<2Vu~=}hX|Pmo z3XS1MXo7$XvBwkCe1T;b_|l>%_G|D~p1}k&XJ*wI06`U`$0WOkp;f3_M={!d4E-@n zRqraf&Ue(M!3kDLjAVGNjKbsS`>~4Vd$`Q?U&4;g!DOSf5R7(u2w6newtJ&YW3?Jf zwQ*q>=+ASn)K^&=DRa?(%>QGLaMX8Bs9ez zp;jsdc^^PZDqCIV41r?@!Zum< zol}1xrrWf&X-Q8x!Z;v71$8G-K3q>XI5-D}FwpbHHZ}6hI-3INf+IN&)UX1FLiJ{4 zMvJ{vp_j-WZP`~8;Q5oe>(S9Mfd70#e6Cyn(4xZaJ8ml(>xxwU$07ZTX2rf5EAq;x?* zwV71Zkg&nB^F-=#6)0-8`2Vbuf~ zeM5eG4Nx_5sB)&xN^snL(0kNZNZJ83uO>UlsU$4}wcjunl<-73TX4#crf8hB&n=>Uc51NO*!cDA1*6Mvwd^MtOtXffmfPP&uG=OrdXNU zwqa?azzhMAWMXBXg9)xz!NeuC6e2QP!mookrjQNb&i+eDYZu?!xlGP z;w4c|$f*8jNqs9GS~j7U7aPwMX{o=^i4;Fw5-LCFv;yd?o1?rF&34)%MFe|-351UL zMmNv6oCh>eC0*Su-1&A{Z}j;pIopgm+$B-BB)O)fs&W6GgCuTSBfOs>tT#F+fH8F+ zC+agu7=fh>P)qKLaW+eb{jz11i4JVAubzH{%_K>g0OIMr#>iHw?A#=4A`XMD(c{~PuA%AxdRvYvO6O&Z*Nko=I4*Nx zA4Es|MRSq!>24%$?hGDJ$JE&5mZKQsst}^#M1MEPHu5yVUh;2Ig^(W6^378`W}@6X_KNfG485N z%GXh)M;W<{ffS}PG~9r-oHrIi$sxkR2>pWXj+Ga3`cWC3Wx~NX2N}{cwHsM#ma`u9 ze@h`>2%EGnv?Cu*ml#pV8kT{4 zkjuD3P>M()XWSH5+2#@j-hFzwXdTY*C&Vos^lf))5`1jT(1d1d7>|g79@xwpyERk9 z2Xj%ukPv~*lz59lNEBPbD#b&~B}#8y&7Ha>0ro-EBaR1z+Ah+i2Lj(s{sy8Il9N+PfcNXBKWI*p~X6a%&^*(BEGD zb=tUU|MwHAm&lm?F1g1KKc?EyC~KbX7W56X5Qe+#<&`=3@+ zi(g5_iq;V{ku+bWMDRHLKD~#*zR{@32}lZ*SMBb@V5zN%;iomT$qBUDshDgLc@YMD zhT5Fc)Y<+4aH4`;{mD(~g4k%5=U5YD5G0E>hO4$bW}mI$3$yf1-g6y%GNt~9>m)ii zxW1{9LBl?L-`~nCdUx$^d&4NA$f&Y`zb(zjqEXxUm;6~N^d{q%HIrR+y+*C+z+_>| zqR<2SfhQ;PlrUJE#&Qjnp~7c`O&QP?t6*>VJ4!zu?^>X0ZJZ$$U`@h?8`K~)<-#Fj zBQcq5(=9@NQp>xO#?NApnSHX&?EMUJzdtrAm|K9)2r6*4*CZ%RK?C9~3T~mISZ?Y(i>SET{V3_{8r$USNQ*n6?l=3M8f-+;S&cncI$0cTV zNwDiAFrwb9JN|5*{%ZB*(0tN;>vMOP-dnx)mNfZ$i+gwbR`2f8Z!mQlk) z2)5V<^HcY>-nSj`*4j0F{_FgXoVF)jy%X5}*Myc^Riw0D zC3lIj)5my-WLRfHY)fC%jAUZH3=q6>q*jZ_5bVOZ=B7FS-P&Wc}Z zgvj8r^)2Z8A-R`Olj*@8HT8aUQa#Q6!(uF_5B|l3V@)vaZ(#`V-$Zo-0|m)-Yf8hPw^`fo#u;?#m27}D%F6hYY0&h zh+F^+81kcr(%zR0dS&)OcC=K)5Ak&~h8>!EjQx~2^~g3~?;zdD_5@L5Idr3Czi)wr znQeFEu3KH?Liq%=hlOX&R8woeBSy*m$;u*zV9W0+PR;Za`YO4%Z#$^Qt@M6vclIgW ze;aCFUeHwU_#md_@~x)tugu#UgAM&^owuIV17Mtly~`%2>*j`wga$KnUYyC0G5nO} zjW@#7!IdlK9fSiK&zSoWveUnFjj7YaK9s{Bj--o#Tw!1yV!%0pHh5KH+HkgqJa)}x zQp7$fU2I&-Bf*Kehffo^D;xe|^%vLqM+!S)>LSx+6>2Wv?`&Rg893H0eFT3Is`}=e zA5-VTfzS)bbG|8FyU_kcvG6N%G5eqPLVd>1BLvKyZJc)v;~54m#mK||T^;0os~h)j zFRsV7g?y`W4UkRCG1#!rTvLvl%!tU9MUtkRmO=4P&Pov;C4o}K+H{xVxJ@nzb|F^#@*D>U969vFbq?jvR!f|aX1aMUIZ)02eX7b}Xy z9#|KT$y_{Dg|c1u0YWm#IE|6>%FK5yHHs4TgRjc&>d9qHL znKZwt++}5KF!Wqq+9zWgBB-bA+jD05>f0Di_Qk}pZ?NsRF7JGPNBa~~=xdXskfs+t z#bH%-sLi_p8Y#1SHe?mPGWgtkYgv5RSkt9T(FpG!=^d*9R$WBG* zEAgM#tp%Q#8CvOPv=DM}4daxAkLq_FSEiq>e~x>m(^m+SfM~Sel4*m0JawwHCjTk8A%#<%^LWv9UV!mY$J0qq%ewpreEHLr}kvz*O0d!Xdj zyFYKa7cRaREqV)=573`5ePp!-ooIr}ox7)U>-y*Uo$sbEPq*&u=^)Qhn=lSm>w8L# zhD)&lJK8{B)x;D@2}oz{M+w2aR~O^Yn8(VUwi^+hID)y`sV-mEax+nWc_4V$oici&1=8>?)Ot2{Wfgwj3pt-(N)scgO&^62$g=g6b)HD z1+YI-vJVw}EJX&3W+;$Or9PPS+N+an_3n`HTDzvM{uvQU0Eu<^t(>_UlEI_>g z!HOi;Qo2{nWrhB#BLH5pcCJNT@qS{r&fJtCc{`JZ7-dTSDwcliv}}S=dL{+ z4>yh3@5xyxr}H9u$9?%!wedf&U?+x=HEHBcoR|u#doX2xp=9I4-`(&di7&BABVpaD zqlTbuV2NbFj0g;(27{ClDMt(8L$VQd<6yEpLFZPGB~t1y5x+cDCN5Z5UyIo8lT$cM zvdrk$Dt;DyPx8UISm8nI?Do$m!J$iUt5Wyoj;+@}FYH+G z+P~U-ac)hNl=^%jp@1px^Kz%By346BESOqAy>qsTA z5u7I*7G8z}5YmWlU?@%tJqXArRSVAWbYb`Wx>NAyo!H(rT++;6=NoO;by}OJ49)c* zm6?&>{(ATQu|nC`Sad@cDf@=}tN5))&(!2oA57^r^9;d~*5zYQAt?0kV5m&-%Lgjn z-g|sM4&di|q!rzryvoFD8yzQ4+H4W%3$)DJZX59oQP7B_c6UV`aJd3{QR%$vr?224 z9g#E;24N$Wt4=I$vTZ0+QbSBQ2w^Kyhp-hKZ5HoCRknq7hA#bxrc|QuNFjt!0pW?@ za1t{5;b&|Vgu`F1{xeT~eUa=(UTTY&%p=yx8}#KOE^Eeqb3lRMg8#5 z=exhcQpdS|W;L!IZQ1#~ZvLp(TtFl#)q2!dP%6Ol`K<@?U)K*#nTL8`e*F11XMCe| z3iYn~pWiX7=4dGSgXsczraLff)_?&E!y3PZ4W`@iHx+rmj_DeC4Z~GARpcn~W2DM0 zV@wu-W+YU%f|EgJo=i+>n~jIo1Yu)ir^?2Dn&3yM6wy#e6;^(GqhJ{Ms&3}t zx&~4e7kC0D4!p-BZ`&+yg6HU*L3C{)d3f9v%x5x4z1CDqg(ex#M)Zo%sU`L%%*qCLf@1_{VNqkg3nch1g5i42ARR_Dp<4zMtpm z^vC$OuW-gPs1hP;s#3Y710r!xi>k!uD=6!ghDv)wzVHg13%8!g&9n8T*j=!;Ncw0C z=tDi&ie6KC96f_VY>Yc*O5Q{gdl%3sDPAqsl2B!DZfb*PGV=B6-r2teUT!?s-53h@ zAPcpe$d<_`Xf>X``yuH}vX8Q@pRv9BcuO$YGzSKZ@RC=BD5q!CbyKFb-YTl9FpgGW zq#r)MxStn-PeO(7^0X&k-M{5FD_*fse(v64lb*3Z!x%E(#IU>#`kT%4R>$c*`s_nS zkZy`I(R&CKM>IQ_PGFE)*6l5t7n|Tc!p4_AAM?i3;GnpsWYF0R7{I00dE{6JV5o?A z+luD}vcH~Y8eqayZaE>{g?GQDpAuhGU^+GmqoS{g+NuDADG+NkKCDA5m{J5y5k|Jq z3`^VCq9jyUl2Qb^w_gMOADesqy<%W*7fwJM6Mm&U)0HJ@t ztG1s?-&-TK+4N)nb#^>7u@}6It6#fyWQ)H~RMgYr=Z#^Aq}NxsSkqr?kNTiC|2bK5 z<=(xI6Xsddfky=Vg^00v{?O8*hb|mm=SzS>(gXXcqELek(;)l(zj6NZ0H0J2!%C)C zoNPd`pi=!f%sR=2I@6JnaqWnN$!fvy#pLVN60LV1U$EW2m4-1Itj#*zKw?gP`uYmB zysJ>mA*g#MoPjMzR9xUw9k;9!oOOpek=-h^2_vRh>}y@35bKpe>r*wdD2pwqwtW4{ zor|3c?^Z7Pyz}!Y?Vcc@GUP&bcz~N&(3oY5f0bUgVM8U+T|M40dob-(B|^F>@n)lB zAJGt1RrQ@r{7ZfHxQ17qsN+4<8&=ngyXSjszjHTEDoGw=QdKvL4$kQ>PWf&eJb#k^ zd7zC)==nHSp?9Q+)U3&sZ<*8VoLBHs-2MU^VDC{I;U3jf9W{%ofEjQx!b>u}n}>KX zFV2X72iR55gvh1H8N}n@MaN6#?3b~wj-OA~4_g<6Z5ievCidj)l6=<=#5|uA?pd`q zz?;2CUZ;4NiQ||vZ5w!S6*Wool`qA35nR{{KOmZzq+gpP$~ z%@ETcV1auzKDN(LMeRi))BF-gEd_=4aDg35BF0beS!mz1AB3A7YyNa~eB%$7I6l+r z!u!Y*!Pyhi)?3Ha4`|MLm2kPR_U)Io%S-bILSJ-~sn;($K14Ykx5^lg#4zAVLl+(a zLe`}q0;OMfVD8hAxlX;BLJN;oMW@SSx`V3(JtCUOxm5P~n)D?&Y9mo&wH1lM4;T8l z3Vj_OB|7q|m(B7~S47qd)`$i&077jGQ`N0g$Q*^r7WGyX?D!mEzJ8n&XKvJghDUz){_u7h1;Ws^9(F3MN3;2hErT(bq(t-#z;;sb}dNeJoRpjmP~)IX3STf zr1kBawPVGF%WCEqPfo;2c%;82hC41hOQyqwbleYwEU|8J=L40sMdAIyS{`)bj2e6f zJ>QbCI*WKuasi@l^^S=8rkd|=_ZxZX9s&h4trIy!7fK2(!e18e$de-XF5WLdjx#N0 zhUxLD9vh$oL_McLIi>|x1M4dnuO#o?^%*;%@KGUCp`)-c=*&a5fwQ6|6;vQiP?jBAfD)8ptiIq3Y`{@CPBnfPJCG1KR$ly?YKD@x*dEG}}o$7i=XZ?{tE zMb&ckE0lK%buOsZQkbtn?taz0{~~Io)m#()1;L=#Q25VA9xsd9Z+!gr+bd&6*hP9D z;_NrEl-Q&)Rr_|jn9Cb&d?K>wSZ-X3&uWG-UzX3NsA;9&8?d+ys-0I=N}g5cSdmPz zs&VESM5(Z6+D2%X3I@vOY5>Av{Sp&1!E@$@uvi0~=`zSelzd;fw+CDCUiiaDiWjGz zoUnLtJ>gwUiE=`8`Y6)!4v75}lTsMK5*uLhCJY*7r#x_+7rHnx9#~v$6v;A>>OO)H zd&eJ8eto!5tjRmkwptrK7zUtVChJ(`GrX1BF=1~9sIzDVWoJD+gJwQf6xdYZdRoT5 zTi6?UV0w(Pnk-_HA+&MHq5H>48~oL8*+Nfwd|rFnR|!id)P90Xz0+(Zl?T-cra6x^ z*^Y=Z!L>&z_zI#+AT@SKvc5IYH z1tA+?FJ6|Ie!0qcO0~p76vqvrF_;l$@=Gv{i@QpW3M_wM2e;Zc{0wkh0v1DlvL?{~JI z&ade?Wco7pY24d^^+i(2`Q8&(HEwc zWb?Iaw~S0^Y{*I}^Yx|7R1nDNBRgg4*8EOy5C!<^s0d2wCHnjbk;9zdE-pTwpGd@Tn{92n9!E;PrNpMBJFh!K(2`N00N*g~=I-$v^%)j`ygmRdVU=zeb<+~e-q zhd&jv{5DKD9D`JdZAaL0B3wV)vlkeEVxXIeF9dU=s2Y7{ZoT;`?xAF0yW4A#%37k= z;e0)*LRGOlQ^h!AFwp0`W-?vPYpi&@U_K~$$;lTyU)ANF*ON|M4qMd8ohhk3nnZfy zQYRvg14ldecvsrGEpOSr7kqr9$RMdMX!s zHjA+()2V}4pOVkLTFP8$^jHT+Z7jWCb~u-$?jTcNOZ6kaF!han5$C7wA4lm3h?k@r zhL+9@m7w%V319I~#ZPEeT;CxjmkR}&%n*w$$l2L&=O;nV*! z&HUlAgWjGxkatLIke3;3DH0U+PSs){J-#I!rOz6XA}mJ+6}8xAe7SiwMQpE|N?*GB z8aD7S&1a_FZ}q zc5W>zt0CTSos8NwKS%U8xk zsM+BI52IjM%}#4Pw>N&=@rVS4<5QjzkZQ<@eG}rxt)iL`U@wSj)~c_0uN){yiIWS0 zm!#DDcn)G0Ivd=sw%WTfRpoLRddkeNxk^8G!w(UH8Fb)-dwbs7|5#Xd_7V$WZ-~P= za#~K7xIpi%zG~>m$eS2CJ)0-Fh?!bDGHgn^iiN5Jh(OU$HA2eM3+~YZ7*Jnlk;qp1 z{OjBHBLvHd@ucbiEu;@j$~qKv{%^-t9WyD+@Y^TrE{!0R>ajwqw`5N7^YsUueWjdj>S%h2!Lo{X3t8Se- zcXwY3wh8m6H}sXApB-DB9EAsD!&)*LvEf0?`pAfLT*; zFVo5gkc$73E)3~`3-yd`)I}>L`aPy5gu$G4xYhM_V)$ElA{h93KVA{1PjzT;Xq!%U zRHN@u48~x@a1zlwTlu0~?DU28-&r^A^^|2zee-Q~@~dZSwE?$!r9Pt_ZN5ZJeEm`- zbW2u6$FzGWN2eTil!mMVd3HQ~c z^i>6MxM_3KYj2{9pNi|?MdL+ET1-5@;T3Qpofs&22RR_Dk;F%jypCc@8iYc0J*f{fERRwDNrUx(g?)CDiQ$A~{X+xDVk4n9bkSk^ZdfDJs-= z-~w4k$`H~O?*h|TTa`5Vs4s-Id|LxlPC!2dp~SRShb{Zf?fB7R(j*u@ST`aRq>3>7 zFsJv_@u}ukv2BJ7Dq9Pf1t|@$DGf;R>Tg_eP>q+4wX7_p87$7KfYkVI@c@hmo&3Z3 zKs@iHZ7!V$T1xpO_@%2L1TTdU;+f%qY8MSEazwo@a`@q+Ts+1_0y`7?9&iSG{o=oq zbfkY4DgLEw+}J;b)9)Y09NXzOn9cT%Z_{${uB%7H7s7E-ZR*W#Dai#xaWr!mD~y->JH8Bx!7E|Q6=a`U$u zb(2IGuPZV|>glGz7{Sv7Gi2r}>eJu%{^k$$R`_+{R_AdOAJZ>tc9od{RxO=|@MAhf zV}sdwDJvrSREeaUhj`{Q8m&0wd#}F&bC~FL^;Y)-y;uTHY~8f^NY^i4P0?89u$OyM z?f062mJ_wS=SspgRN2A#Qpz5M1;`ibg@lB$PDW*%MHoe){XC7NjycvuC?fqOp)sSJ zW9O6vY7b+ZY6H}x!4Ug86=Ew0@R8?%Qcfd(b%>iE11G5TEo{qwx7)8)@^$$t(^Jc% zdbHSnT&TgSyQP(l9dm{XvO{`+{U)L%QY$#aWSSGNy_Bl*nU;xwI6tU^-u}yySG&L0 zHr`DAJ*C{2iJl~8P8f-&akCdYGYdW@^!MoVz;x5RjY2v!J@G;iRd!~sd>gVb!Oeb! z#3ruVzD%nt@~zhrqQ^vy-!nJGg^VuG&&8nY2suvQ#kd6L8obuZ0@)c*#Co7@bF$3vg zyCbqX-D)-nfUpe9E9ri?Z=2U4X5ASRTX5DmC9~G_^%>w^Apt%%;$K^7GJ}pR;T3#0 z*i{Tdr(in;!3dU*jO}RBtfoLDp>pZ?#NMHg;kCC4jlw-M{9e^tg&740?8Or(s3%&+ zQ&4(H?3TLHNK&Xk(Xq#&R@|n7i78Jcq%;kXwanKo2F`WNp!}!X43V(vZ$=Ml97| z6!=y?9)ZVEyF54z90GP(*A)A)$vp00f{0H>-B^Pd$)kpgpkKqQ3f4Gdp`|<_1#_oQq5yVtlXZdhiHCN8 ze=V=ChB+;uyYmX}XbN`+*bGODV#G>)(lQXARs^}zR~BF<2? z1)dMpA}H~~E&PcS1xMxhu=(EIFw3SGjJXGKLq`G-z(fT5F~6nX%G49|aY`5qcAGy7 z_TcD)SXxZML39BCpkLOZ6Do-(kSHFaI*9VHt!=Tb#!3&9fP|vQfLXWhTY_}`;cUcf z@Oi0O^m=T0Y-Pn=WgJZ%D|=5-0yR(}*^$P8Vy{U=;sHcv$P zuz#EZOkF;rGB(3<79=f|8dAr50n+_&Tqy6AS(zB7DTJiunW8lBDgBY+k)UB_kBy+R zh^@}MdYaEl#Wv``h6R-xa(GakxFz!~R+0=n#OI+ca$@ma#3;0s`zBPo-!Auf{L_bz z#98Iyi5mtOW{j*eYKEP%z6U=J<{tDGV@}hxx%gA4SLKCs#R&7wZpmSGsLD`n5hP?5qchbaClk3Q-hw^tU$e4I4#y@i=Dzlqnm0^5oNJ1_8= z>#I|1cm4cw$Al1qxj4C&oSH4mGfn2G8GU5+1m2y}bYCcBEN*ND4T2^npww-`%X3X? z9g<=*jc<0{HxI33V0v8!ok}BOmQLyE`Srxwe&`PHG7PI3v4^pSAd=_n69f1R&#blE zRIqg;M$BZS=p^JDV`W!hvMjykraOCx_Tur;Y5Zp2=eUQr_P?jEO?iOblL#MrsW!x5 z=fUwNWFs_!+7sjoIXd7)sbjgq;$|43|qom*!66icncr>QsJ}rRsl>Y=V z^ZD8@rRbpK7wY%VgM5g`@tS-w=Vxtm3_n&RvX|yt+QmhM-fJkg>G4d{yT1V&{Xa@n zvFfk09DG;lpn7`UhgnyTd$KUoFc<;PHReDLg)B;p}4eydn_LP2fYI=Qde#;bN z(9)z%hId1t;3aUm=ie4T3Ib$;%iPjDFqLqoJT(tqH*YW$Cic355w=Glrb8Vyr}YY> zGi)JJ^SfHv0(gdT2_9n<>730$?wUAdY+1+6K74fSQl)@!=McAtDj1vd)$W9;spEVh zPz)x3b&%@dWJ`cOntk$>Yf;Nh()c#p7xv%QZ5N~LoERspEgH?WahsmX8FqWLTaZMj ziMRIt>~*`^Z=4a!W<*P$_AESYEbG2UGr{+C+SQv7LbqW{Twdovy)fW_cULLHna9`qXhn*9v{O0D5&n1Iv ze@~os{}gQ-e@|(#`5qD-5SKC|)u`q#&`A6Gd_KxCPx4*)JBFuxC?P{=iA^ORv7x8; z=DbiSI%&eFP?x8p*{?`~e)HR*(i*(6Nd_{_Bo}3Ih|;)7GJkHe11(1XnQ}rsd2b_Q zI-Z)Crl1Xg-mLaJ^aj>mpt_R-5{399d)=qMOhC1#`9w>z|MR&12j69#_N+@KYF1TR z$aA4j(vCBi9&(Z4l7rt#fe*{TY~3D`Icv!H^6P?vm9gk9&n&;F*DJ8Lu|oBmI%|t# zPV%dRp)}k1`E66s1%K`hkx^Cx)!1Wwe|6O6Sz>^CuzAp0%k0Dx54&1 zGcIUwfuGS4p~G(@$Onp)Qf~H6GOp{JG@5edS{0GTM@91=Si_{!ZQqJnf(e{K&%3Y} z8jskjENKy{(xsWP0puDTPD+h`cJb#Qt|tS>g&%b@`=xSw6MDaxs*B9zC>KWCg0jU> zE@mM@VB4t^pGMEpd>tgD+r8%H^t6g55Wse_`aRO!?XX-^p#|DEjrEFoC4-5W#M#;y z-|kKwb4ZU+f17Y+XtVoqBlaFs(b)cj$^DO!r4W+Jda@tT+8g8Eg7<7_vLf>&e6~!E z%^GhiQoQ3Qw74C478H~}PF5P}_GqNo_8E)SFa@2eAqOHwimwhC(`$QO_8Z=-$~3_Nl^4Gf0_T}+C$;ER|*RmCx}_!ru@27}TiBHthz+-)lK7n+oHiq@#jBcHwsEvvaEhA~cj zi~U;Kv-Hb=GR9{LKf5oGhw zg6&YX;=f5?W~)eiON*;IggE0u?ANa?VCEQ#j@)IdBHtsk=nwnAjVRiIiG$cBV{O392uF*|hPU;For>Y@5{l~Qjuf`7pJlwo|+n$2Rsa=h-Le0u~B=8kShum4A{x4*4#e|ybZOi7l#)dSUtUY}mPhZEd$y4Q#i z>-nGkHOZrgh1R}@`|auf5gs1MUq0A7I*qx0Slyn^!LNUhw!eKm_!W85GfDpJqhBEs z-wt;FrS9F0ZF1}{T5QkdgSm^>pZH#X;CcOt=k*7^|KQ)km?uj9TL;2XQ%X}1Ybw0RSn&6FV%`qk(WK67B{dKdh3uGM*)zyvOq6%cDk!)b&e(@3+<(! z{Cvs46gqv4vz;jdIY==p=8^OWrl}i%a)h`n@Id8MwD?cQjs($@dcW<69!iPmFX3W& ziK~98xIbKZH{N#(`-%~d4rJQPh(awX3p20uc0$nty2r{&L=@$E?H_@xG65-GlR(OF zs+HISPM?5G=BdG}Hx4Uvtz1PNq+Q}%HO`+KP|#N79ZnF>PEZK{y7vaH&-d-*7a8y| zKAC%rg+E;C9_PS~z{FJxUaK#tg_Ap`f|s7pIgBo7i~sE7gzn$?dJ;)N9X!4%;z5}k zkU92i@WfA%6IYLnqes`@8a@K2+^k8pDtqsRQSsO0Y(!li5ZQ=QhkzRH0=Jn8-H&GhwHHB(D$oBlNJDI)af4u7T2HfmgWsp2E8`Shdo8Cs(<+M z0v|z<+ceH(&w{)!IQRxw#KSM6*`e)3FU6E@GM7mj{pZaldoCtcrbMg?sGvVJLBLzXWif80U40;6K23q995Iav&augoYW&fJN6& zgM_t_o9AxLJ{kZ{Vo!*mMb`?HIQLTTkNoUH_V?nHHk4CWuk#p1P2S7!OeLZDDIfb^F7TT_<8O=a)S@1V;-gF0UbCTk6eJeO?`=(_B@L86lgDW zTEtH${1|rvkTQ1_ogja%-1h3w5ko#!dTDT0;T&E@@UTB+veM>RX(MyJSmeg@#)-Z^ zT)dTKIW7H%kvg-Pj+Fe-;i^cf2AR_!T4@1g?X=Y;Lx8tcANVi&5Ae==Ir&5AQ1&N% zu8+^Oz(fz%#+T^w6s$kEH>>yf494K+lYPZ~=+1>5q}_ll`abIqSNsoofuH7jqLsV# zm8|vO$4`PNs1;8yyN&B`unsG+C_&c}X_ZMFK=a8w!~cLrb%YnX+M#Rya3!aX?<&5% z9^|T^l<4{e**BSrX8rK8s!9E=&@B&0#M5r9TBtaKhWDWcGU8eFHuw_`(4|a)zU$Uj zUpg`w%6Yv%x$ z0-z1Q>2mr#?$RIh8?UqC=KY6@w{Cn;`axfTsy$Xnkms8FDc(7NNKXTl+mDTSW;vH^ zSec8D9n2oyJJDHS5O=Te50~1`60rcX$#K=Y1uBuHbCya!Jl9s_0gZHvpx*+g zPfz^e%DLMeyAcJN`kiPca#GrZFC37#dSlhVlpByDA)t^nfo22IsjJGI5aX%d0cov) zPoM!EKI43_$&IviE3JtiB zer`({s<+lxJ@?ZwMbrej)jAik#QWBdL!%tMagzIsF$V|Etb8Mj3-5RBy!00?OGhlx zS;Rh(@3#XhK{sw+JZ^3y+7xPVQrsqVlV-saTezR~_2icm9m2k&*8r(jrFkhZ7ob?8 zH!T|UGn^H-akShj$3Id0cpT>N9#K5Nvg1gaQs$Y`M7>{2GqcF?VLg9q$oIkry_lmN zvS5W962QdlYR=WE!F4n%)7||yK;%elNE9feHl+d$B$$ZG;4yES(i zG-b}l(b|aTJA7vZ)0KNsU&i~&8e)2n3SY_Q)5G->HwT%jT6kvPugzhm;5 zQ=qcGjAlOMfah44sJ-a}r6DBkXu|`J)sD%0naG0-^5m^)6JHT^eV{Id+za?ZqlT;% zF-gS1s^r_joi2QTxHc(Ny`C!u!jtdp-OG&3XM}MNt>|mAT2&5qD>sGZ1^|aG)Ir;R zs$={#4*q&*2_8H%FGMFqdEx0|N_8aK30sf|eb^nwNohvs)>PIX#oHvgme!uhNQ zWIS|0_W!VS7H&;G-rpbLsDX40loFItqYPH$ zZ_(!9Whnl^NOEstBsGXu1&Ab%Br~Q#`ZMZeT4pmJqw-ZYPSTS9s}(Ei=yeR&F$+O7rhoRM;W11m#+*+J+^tYYO1!m5+%N z5)E+M)8$md7t#!MLJQcis5-^MI((u)E$=>+uT`z~uQtBA)??o|K1gDX!^5*4hRkic zTx3{Q5FP~)K~)gk2g>nBc~X`8vaSdosg(T>z*`BOz!MeIPwK95K?Pc$feQ>U&_-If zJs>7DTT)^=^g+fS^W$&6y8MOo<kqvH)>H?UydX;b7>`rN1EryE z9L8ZFNQCBKcggUpzKhryRBW-P!o>6*B@^XPm|+LgAEx&KGcH`L2m#qCKkD-0NsEbA zDSI-*BDw=DNOVb+e1KCGk{^!hcVvPv$7_~%e6^|EE5|?$ZJNm!mSR-j9 zZAq1=G$+4(g-@r}xbVmJy-DO2E1ptcLJ)7_aA#Au_RQRLDRQ&~U^jV*k>iiY|JuFY z5=;L6w8(6i^T9xbPQrL=pYDjo!81hK>YFzi-ZF??RL3q}BqkFFq!v}s2AWzdU`C%6 zhIf1!()T?`$+PYG|;aP1=7pLzf+1g@6aTuRjaX} zNaa{%qx*K(zD53Db{!Y{jnf$;-sOia4R&&Od zHS(LrdDMm-@@3Q)=)A_$7$iTC=u)x?QW4mumdziHBram9D>B&mGN)6o|6cf<`rWMk z)rkC>(FD{_Y01`iPQK!a07!0)0iBc1=G-tUEX(%jmxH;ZRy)7jDEBoUE> z4iI~ZNI0`-Ky{xcWg7qsk@7B{4hf@oNn!c%yy4cBFjiIGjoW7^QakA%a6Yp^-vD!_ z&Vso`C-H7@yP3Vr#otE-e?a)5MOK`MK(2BBSdh! z(dP~FL>2>7ufFWHY&)hDBh5zk?cx6brr`QN3e@Ln!Zb*7zV%D$$5Dp=ONPO(9Vb73 z1IY=7ye@(!D&Eayb?iC*$#XiX%9WL`);Hx_9E+EB@)-cU(obE6j{u!E*!qCSxW5oY zsgnXu+n1uS_1#c;TbW|B?ejTr1dq`?aWTm?UEBG)E34KSjTK>6*M z73QoudddH6(l-67dYA#aaQ+ts?{xe43En^yP?CYf_5E?Vfu~N>AB_3 zN{IU~*h#VxC6Qd^#to+3&`Fl@BA61pr{Ma;ZU`bqWfC)^5PJVdfG+I1#EOD`#Ecwv>e2ww?*T%P(yU_lB zhxkpnrj5VqD4i=9K}^Dh!wxaGBrFv#YCit(Z*eDi$~oU6Y-)m^^P(1+du-vIn&2WA z2dTd4fA8{PaSt9fnoGkT9mQRM)DvXkJR+P6p!j3hyUT@Sl~UrIc=0C0kg61*eEIT{ zwP^#$ka+D00y;TustO5&nUlM?w?6%Lz^Rhw4>va-6B5%irrzjvWOhx~vl-&EEY3{v zEiX#rVnwpyWZdThQXwx2i;qmtxDF6GQz9)zwZSJKf_Ro zGiCn$wE8Ae>cyY4=;@P!!}&!*MK?e_6;V45X62576-{z7Ut&2=plXCpP_rltN4x$R zRy4cjPc$+a))t}^`;c3*Wj!9+n%M6!(Hix1l+3qQ-3uKUw+LnfH4H$CR?0mHS1tCI z+&1V$OS8{~5ol*+hZwq>l6xE};&I1b0^!b5-YXUmuBCk8tncZ@4YN$W(gMgJ)n@PEgCTH zrk?BRqFuP_G*zm#QB2oSMDr~K{;UXFRW`O}|B;GSEuvy(CK38L4ceYUUDu`+f~5`7 zqtxxJRdF?LL4@eNK*+Fv5KxY6I}h9g3bn05DlLtpvs;&Ibs2Zpa{rn=r(}8QD1cvn z&RAky^Z3d#9B-}0qVe_F$pfPpniAGST2XMJlz1WL4jC@ow|?s%U>Hxr@@_Mdv|kfmjm$JG0RiqA8B9H#MkJ?gCmJ!M(+R z0r>lL9py%E0_Y+#QAqtcUM`WcpI5WJ7-K#I7wZFM=Cr9h$&_X$-6EgA{rKQKM~yab znqx^Al0JKRgROXP9VnqZh3@QrWX9)0Pp#)w@A{-&z7N(rBLh5IPZV{5Rw-gS5ArC>I<_c(V?bI5CP>!vjSLNm6+_m$VJbso zFvIqx@Zk6!=3A>Th~YO0+@(0JE#2MD!fJ_%WpRMZ(4+|7n(91yA)Jo66=N}q$i=ql zBqEx=J0wTxfEtrv{vxb&H$~&D8kjGQ=B1=G%LQ$NJb^V6CdYuZ*1}w+yD3GJ1T_DE ztM8DmNRH4oS?ak}pO><}K5wyuovBh$%HT zGp7gQMNtlG3te5TE@GYy4PwHSeZ9x&z^$s=Yq$Ym?9f-A5)q6k=`@=fGb`U2+yO}w zscOeD9uurb%qq#xdY_=dWT(7Knsg7aIH0_2h+B+%Y_dmRq+rFlH0L9v$r5ib-Mb@T zx-Cu0MJpQc4hMH}C4MMVYo2Ga`w#HG`K-AaanDZbyI5Klk9eg{UhG}nyjQBzTR7kK zw4lE;ymNxKH#(RtCfb&R%t0DyC!VOLuTdE-32s~M1u`xF|7&Q^LPRI z=28m|ltP3TU=@<5Jg)U^@wI8lJTWUbioCyXbSHoz8FTwJiqUPC>(a*>$U%Z`X+@FG zoV>e^cbpA3p#FKAED^*TAUngd*HVZo41=8IW`3GZT)Dz~x~A7)cNQ}fvT$gRLV(g2gnL96?=HKjcP(u&aOuLQlvCf7 z+k*Car(X=7tCgIn@Sy*85?P8nud2gkfBixVwTc@0PpeDDscJWr=_s(Em&)y4qErgZ zDfTL2Vh*9GU zmpvD28Bd%8!{eGlFl&JTp&##5{Af_$N*bDMO)XR%);azG9MT0TtCy7%)g8q_EFE%G zaEEx-hVZO+iJSgAnl(f*^yqy(F;7SEvf+Ok-6h+*IAz3ih2p1OE$wm_qi{=*r`uks z)pcVCZvC}5cXt-VL9B#b#TgJU^=-&lI?5>VH#vgFg*Ss|OOHmqf;93$Tt&BWa7pni@(;Ye#gmrhWGY3+1U*>4`Q*zc$o_Q- zXY$=!Rj_@UQ91$QI;-6ps@WvB-3jP!Yj$!!yJF$n`KM|SJE8!NLXdOtLK-&~<2;(^ zATj5&mP+w5#BkVzRCBzHkMU}u^+i){svrUcD@qO&D=rw=NPknyMCNd8HM>-9*qL$X zAE0uSj)rI3w6hMh9L<`PxMupZIK^RPLY#44ElAb;uMEK#hbmp10=rh1rKw;VhI^Do`2eY0|^2Xm>?#9ci&e#YcYKAZA3fH^P~Aj zip6WECsat_qPalI*fH2HtO&<3wpkO{*Phu$Je7LyW3tnWhL86QT6isgn6Lwo0a2nx zOExRBu>(hXgRySG&OR6JNg>^_K!FT~y-IwyLA!TnfmzIuIUYVkDx|o;hMaVDJmP$QCl+9p7kz^P}ck@ zo`v(Ky4sOtG5ii)VO3|Ce`FQwW~uk3gM}fHI>n;@!vaL-;dH*6$0uH!;#-vgB3^gT62pR9arbUwXMaQ3Tf6Jcn>8|HU_I)pt2IZ47Qtf&TAZTr2U_lbqSjR);5WvrB0mi z1Qj-a5DqK8sNXMfsH@pk-k7~n;-s+sOuT5&5RQ~h6*JcBu-IGy7}{mxc~ibMorf}; z?oh1Y{K4#f2Uzj)S39QVP4%Xx^}nqG1wJMC?y#`ib_8#T+q& zo!>b9W;RruW?)rZ-R9(aGWr*qsIlN*9e-4nS8ir`-!-M{ONUu4(a8&cbp*w-!f-i| zb;_CAzc`5$ObudyZqrpng&Slc=l^{C%@j`_zzKZlNyx1x7{?e#&C=I7JJ0HAE&39< zHk246%DJ?VL~uUslujlwoO3Xrmo=2v;1y@Ju2|{)RkLTNNnd&v^#)7Tf*a}tUS-JF zWbB9c^YMu$aN$&X;{C?cNML$#|jbb%rM-!yRM4}?y zFDmgE?5tK~!*N?(8dEsMuFUmKjTD}bBIV>XQ~_^Lg#0mGIc5AUZOWnh?BpL{(~tka zAZA_GoV$*aLligoGU;)2{0qu3orC3~b+}8SkN%DRhLsgNg+-pg$!da|-LboXRqXO8 z>`A(9CjDJAA%-%U&eH|^{rxX{84oA>z*L5|B_NfNmyq7&XRa+eyvM79^|VAB7Ut6S zLsCjr4HX>CmTf}LnZAmB*w^8QXYj(Q4Zt|u(pav)Kqv6WJ_g}mye;*;tUTH7L7+EL3_;`Lx51TY>X z{McYT{#I9Ob@5bD3YZGXK6LStu<}~x9;A_y2-*l_TC3%HPUm{qI=920elvTjQ_(Co z#!iE>|IlIbhVrS_*WgJzyktf1Y>hVOEA)A!Oyyi?22lt9Nn0~I&|&$9TrhrJaI{A> zAdp#!GzvA}M?VBzD}a8e^3^*Djm)wvnchh^dvLghaM4#OgD~e~8qzw;2lH6u# zx0nPG!M#}~BDj$RqmL$q8z7xGxv_win`DdKDe(^$n(Y&opVLiq>z_8TeGdYwQ*=I_i9{JQK;kY z*^gS27I2O|KS7U+r@&Qrsk0=8XP!<=$7sfA#B9#Pp+HU2b*Fx*b+MOk?EaSjFE>%M zuPX+pWUurUr=#WLw}p646I&g=@y6W`pdC{cZ_P%PwDNfloliHqk74mA4e8(Q{lEiP znL#%{;L^>r?dcZNm747ZX-%_tdeFvCj*IS_UVe~h(;Y^Eej!+gMtNvUcKbg`zcna? znDPIj9%+sX>=^e1uW8G~fk5jdMU(D~I{yIp0*Uwa=DVMR8wncwT_;yAtl%@XVpMH$ zKCKs!>oQx75p$serb{G+^V$35t4iz<>i3IyiHw5h!Vvsb!EI@5@~&~KYByE_NkO|0W;nT;z(_rA?MIyV(J`Uf!Tsu=px z_cN?%?m*0O1_pU!@0&3Cgkf@g#|7rlp)#QeuS`iYBL8?tV`a-Ky`uQXKY)|+adRmV zt1c~9{QCMI#-78@eYEX`;Rg5QCx;7Zhos=4sfLPh!Z2e6CcZ$jvEtPz&2wbMKtWgE zbHUo25*>Iq{=W|7yG}AI-&8~I3HM}@abjqW3D0qHiZDw|d0kpt;+m+hK+D&d6_@qR zKH0cfC!SN>z9x3X+3JkDA^qh$gVOn(Y{XOAUj5zF?BKiV3Oe^2-Tb@xOjWzzPa2}F za@0Uw9;~v<&x;;c%$|#n7Fk~IqTke~W4<`gvSj>-Hp!)};U17_akr!8Ce@#W^HlIX zd8is*>LW>eM-0Rvo} z#&2%W^IEYpacO)%PWw_fMg6NM!YzB)iy~&NTp|3Uehp~txfy=%@|<6ZD_&>#J%ZWf zAHapnq~aeyzn(L$de@kGRmXZut+6U34!bmI+iGsuf)AVxp!bR{SumD`Rh{#+v~ic|V}t7|C+Oe)Z$9e-?6e>nh#Fd(cPPdHZTt^#>afY`^0g&P zY_P;{?|}?HA$NVq13c0`UEOK5T`_7Wc3RaFKB_qBNZR}$t3|27i{$CUI9F%0C31|j z)h4wJW1$eBHM-S=_(^5< zeGfgHr}!G}SLWjZ!I=t&#{7Qr*oyyZB5wGK@HgEtmBi`GyE9`d>ChZ_3Hc?ZUR817 zRtvFGYPp6Wr=(TvOtoop`tWhphJr#<9hq&HsLW{QN_+ay`nts_1P@DdjMFQ3yUdlJ z8k**&%PunkL1^OV6_stKWh^E-8IoTm73^rEzJr;%Dz{A1RC7#(B`7L8s!J7YXG@y8 z0vGKArPumH8Sy|zsANw>Lr&)uKImF>E**jn!2WT;{VC*ev3LraC4T0@&-C)!t-SUg zLyi#R^5*(I^E+g8JpdJ)n=RAM*(_RE9W2IGc`GE3ui4L~v~G&v{fDjbyif6)%G|8u z!8y^QaAPaDdbOZ(;TtR=-e*jdpjO*Jj=0vr@7d%RCU;i<8GJE9OFH zcf}?pJOdbaL+aZ%fMPjTb)--8w9`^rp5OB&11{vmn7CNt(OU0kb^`^CFdop-=>ysQ zi&0Z!6zRhp3jipK;Skh3tmrM^6YH z1hoozmxEbS*7WuraW?&TtHKC{&~Je{$;<-sG`x8R9}0M-+eHi}M=hp|Aa8c+R7y=| zcCDf}h%?+%a2q9uy$(??h7Yu7sD6h`!u_muBI16OL-CN*Y zt0Hgt!tKn2m44$3@j)A&j~U@EqL7GD;#jROx{*89o{pW|G+9{8@k}V@SjNsy(sEBU zWhk}7hLQi8z9iW_%^eZ`~!TfTJJWZFl1tOXvr;j z(xBsBZ|FlExNMbb=8|rYWA7KANwi2Lcad($94t9<9>9RY;7*ATi!J`cfj`bRc1ZoNAu$(J$7 zYMzBuC}5-=vh;%T!+)&DsI)ZAo=!-aAE~z+u}xB+qSakHSTlXG&524IcCMxVDW>#S zftROwf@$I~wZ^>$hv8B;vo7m>@!I_~N-Ka+FvVpuehpKlhb#1jv)oGs?$;pL_OqTV!tt@2oQSIO%x^m zQ0UXaS8XrW`(73ddL(DX>C1supFcD&RMkT;L3mijb0N3{DP5*`_EP)>G;!3W2=<`Z z5@*P79NKzPG8C4099@*4zpVKcV#QbV-r&NmlJn!m%Vqo5+rfoPdK+))jI(DDQH@WM z2c26F!~mJJaa>#?VyxtJ#a#~L0-2$A172hfeEzgL)LXBsg1dQ+M$m*|42y7`PQuLMQy}Y1bFGGo&E@ z9Ok2=Omv&!Q^6bMkh?-9COoxP=@Tq4upM{)5M9aQKwlSvB||3Gif(gT7B@QZSz_uR zVA&JK4{NL{mC{czYxhX(#hnu$qn3CXM=;}sI2ag3B9>CNKmWbRZ0_>s`8iYc zigf;`_pdx^vsuqu)I$xlYgIEf)&ZY*@>KgJY4NnqaC6wP@d3}kGHv$c)3uGjKN$(< zXSR09!+Z=oxhlUCll-*rH{f6x3_QTtR7yaozF1 zwVu=ZjUwmLiCtHj6bkQx9!GtKfuMtv;idy<>^SXcGAJNr;M23B6QgG(1d!&9yhW{G zhPe*P-!BX1vAh&(R@!>fB3JUnF_7sa!Ff?9gO?6Hok6z@pp5!DHJ$R|JivY zmk@D@1Z{)vwx~SJ$=~75PUWf0c;eZ;K1mL|>}{IOI(}@pc*%&u96Jjfd*E2_+0JQ8 z*0PC$hY-skV>ogQmh5Tj6qfF#T@R0JW^qjABa9}UD?u0qF=>2+4`1529BqgyK}`3P zKdN^-uL1E5A+5MxqKlJV2>jk})`amN!HzhxN3W*3_qk`{Egt^*Z(e;bL?;a#1I8Kx z2FGPHL`v(8a>niSV^;;%`oDP8*p5BAFng~S5k+(dpS84Gz{J!-wB1#N zu|9WPCoki}n=+4&tL*J4V7i=6b4L9jqrK?+Uhwz+QmVLP0f->8 zxx5)>u=!F#CQfh4T+q~TxOUJ4Q~NYjyfk^$y;AM~f@)|cF_Nog-bl=7KM}nO>yPVnOEHXY72U+lEzH?3q*r4Y|#&6@rbcEe2qD;sqWpg zB#$LVE63fpb}`8PtMm`B*qanQLA&1HGzoX?!N|~t18W(SK+tiu-1PqU*H3P|isPiE z;k@Lr{lYBo{sYAtX;mG06mVrnaCJc+t{xO%5_RhFC2mqI7Gv~Ic=myi zPFQl|JXInpw@OAX2c9oJc!4o+WJ&*tm70Cc2uzmNV2#ooIj>4wn-p(bd8L9;zqbSt z5GpFq5O3jSU>BbxH@UMR-;kG4EG*)>-7NWpS(7#~#KMZ9BJ=W_#_Zsvbil7)(#t0I z8e%ouleLwG4K@BVMV_*K_si3eJqgpV5{URvE zC!8bRV*SeF|9cHP}|F}cgdG!YQks(zhW#25heCSbGc>wWmESo?b2 zgnxkNuCQiR&(k1{T#Jd4e*l|qQ-2ss`?danogKSPv~*HyCQ#cxT<|#G{ClGc{ zuOg{~^WDM=`ERC5P!C@+l2l&y%PRNhqF%3z^Xq5kwcKI%0a|2ix{(TL)eI&+*lcG=IDsc9ib z4s{#s%G)h_9)bc=JCe1KLsv8$PHK5O*8I2#`=M^X$ovs*!q!|0XQjm`$1wW6^ zqiCp4$i&5`ibF1azcjwI?h8P zFE@A%NUk7UlqMi*_>)ou@lB4hi_REu4D0 zDV0E;*g(q9TECS!&(Y^5->-dsJ-O)_(jEwe=nZ=mS={RF)(N=lX-=boBpP1$6<*B@_r==7mG2()a7@c?o7o8vZfeL$KS?WzT+BxFeNT_t|8hkj%>n5AjLv%u^I~Yh2^i2>ShA zrLo>KPIoh!+QbG516GBSJyAi?D- zAU`mg12|+n%A(`5v!*!6S06VHF1bmN^>uqfOFg3fraa^CXoRM)!kZQby_w2f?B6-| zO0_5m9pz8hjgMt1aZ}zJE$Heih}#3bRs7glSVm3I+UL^sxablzwn1aR68@u@J7&nj zMJ09}F6uY6&T8{cJD9-b{yNMEyWJw!^0&{rj*w7SAH(3%tJZ#lP=eNcq%7g^c(|;R z8p|M^Zm?wSwX54{hIU#fXEoq4ph)zEiN9-UR%{FjuRw|mnuMyj&M>9&SWyjRhrqQ^ zU5Go+(BM4lkIps45nIjL0G3hcvEFT~wV4;y)9DQLe@7RqVM~iLWJ?PZ(9#GFDZ?MN zrZI*UYST4w_w8!ukU34$QzGd2b)$h!TRlo0n7_|>&2Ho2D_J>mZY;J&`c)vt1!;kE`<<{RBq~zWe;y^i8V*oUCmq z1$8nZ-a`t#1YsOoUQy%A4fj`s(~3ss&^X^VjqM|4p`ovB>|Ud_3MJ+yar`S;E=&M- zuGi!f(cirX5Q3<5OWwuQ;zLTq;-}N|GK*SOl@r z#S-SDx4-KB?id4@sSwxhi#on~?_7(P>G04wT?aGCB8UI6=k#lse)KQIk74z zYMeRHnmIl3i>Z!f4EGk0)SNz3pg|^LVSp&<6NY>j424n+*)R>%gnerB_J-|ih{ynl z$~3+-ht-yDerPH>wt;O}Uz2xh5muHS@%@3nWJpIR$9zgUPb@p|M6;9kPXs+qMlRtU zm@7nYW3Bcn7*WM7jg-juuIl%C^;YGXR}DB~ShBej^aeyr@5m;BDyTch!5OA9&|Z)o zGaXC3wT^N5e_kgVNF2oJCL+T-nm{y?R|Ct6L%<4dvrM%k$qnLq1+e{1RTB#^fx*Vq ztn`;@T;=fQ>6~Rt?-w;XQPDrN4>I&1;ABp@w+UL@xsz~iB)c+c@cKl2gok1=1iLrC zJillD1pR&_+nRRZFa@r#fYqxtJmaKw5n?`ePd2;y?8-A|0wdsJQ&R&8#{!AJlb4-Q z6K0UZgP`{U3@2f^>*ZCqD$qe0X3(;R)OIY-NiF>lW=pa=S5kYbS>b;1S5PIrICTd- zItR4pBtb_#9*n^$KS~8#F=u@y@Kwa1Fp3s;{z)9_b+)E0VS&T=3H=7=gOZbiUv>t( z*AC;nbk0Hm@>s@@#P@TJWDibRvXBzNq4$1Ge7q(fEx_cxES|YXM=mkvFAV2v&VjeZ zvkyRGWRv=*ttlbSQ_IGty1E8DwOdC%WsW~PGhX?*$DBD64k$T?2}tdvMAT<;m^i*L zQMs*ig5G#VC35-q6IxtAF7JG@1+ruOfpU#8i%UFH%=*)5fDkG07`tDJ#dv}^S{>** zCO;c$q6Wo++of0_^Y3n0T^m;c6S|+(4uF!4>A7LfZUim#I=t#F165b8$#X%X55D;t zaKxSD=AM!HY6Gm-?T1|N=t$MrUl$V?m4~P?$u4UH_Na&Oh-P`G0xGH{grjF7#PF|W zf|$!0R24y@Ihoi`1blpY57X@> zX;D^N%@`BB{Sgp)^O-W^$rYhk@3Ez-DQ4$8P!M1rSLK>2UJp^sjM)svV#|}~m`Qxp z&)6(xRzUYZPUiICsnl3MkNg810@-rl-k}YeTr?W2>4!J9SvB!-8h*1nZ#wc5CFR!q zN*;L*iaCtKF`z+d)N6h80nj1O_pBiCTUaHqj#`FCDpz(wT8S4#%^8DpTj1jZJ0-~i zLs_SDGOSLs>;-V}!rgo<3>#Gp6Y!G|AppKMy7gYbi7EjP@s^Mse8jUgT%Cm zvy(l$)7rLZQWpeFzG7U{ikV#syW`EoXS=FggQ9@9ZUZ?S(U*M?B**avC;=Oax${k^f zm2?VF{Bf3)IrA#B!lQoxLRFj7%E46OAd`UW&e0;nlX3__2NY=6cX>9PHm%5m>1zED zptEEEC_401m87*Cx zPXPas>r-X!%(WEe^`_6NQJ;&qAQ^AW)Vb@5#W5SOn)|M=(s$|0A7D(4Ax`kC zvo=lRId-}KoAvNq%bQk)k>%L4Za;?wLYNvge#+BY-A{)GzXq*B?o5!nv?A|3M2e>V zmAEYXy8Gr$G_{7OzWLIVdXpz5l%^gU>XMZeDO$my#*Z1zycx$dPcwkG7E}i7i{fc` z`;y>GsHBjv&rNd(m#(wO2iNo)BrH7TS3fpPFA7P^f7Y~ywpRSefyLhyxD9e*A1x0r zS?tcASXy;VVT|V6Xer=TcMF!R3|>gTjKUN1`hmc{K;Ea1==`)gFPXcFB4yqK> zPq{9+J~(n2J?^v~E~dGAJi98yXZ1Wq%fqztyxw(vvVs`KQ?q7NoB!Z_ql|=n#;KV% zEuM<56=bL~K+UYMak9_>tzk=4&LE=xgCLVcp^x>DG~6oWl(oc!W;YT|P=^L8aF)9dBu9)2t zJjdJY_^G%)ptUjBzhn?6K>!MOr&k4XV~Mtv>0F+!?VaBP9#y=54_a`z&Esakni2&l9xYSl7g8}@$cORjn&m<( z`4;k{8L!Z|3K6?Uf=6H>T&gSb5GYq4#v zQ(M@dAwPKRK3z1S=a}IOOxh6SHz5K2nkuOgpB!567ZvA@n7kvpfiV@&8IxK&Iwg|! zR8Irq!{4&3Tp7#9>FETDL_eeBNIY3~V)a!RSnxIBGhXkC4#C;fZiU}ozn$##Mg{(? z+TT;AUByZI6YKJN0PwDrP{X97Mk=kJvc$>dpBUtH4tH&E5}a26xgq&PJdby-eZ^BZ zSOfJeGkyV}AC+7D#+o|78Iert{6-BHL+4eH{LtNww9#T9JX8amVVlh4KAp;)7}f1f zp`kgRi6Iwul@Ds-$Rl*7P&=H)q_w9+Ht#jonLIx97IyQ<`t3n@)pgz%GU>vWha4o| z40H3j3wJbUJeW^X5;RW~FW9tF9bpuS{ze-2ovt&@$^}{%g}x!mYYoVcIApFWuTlHK z_chN~m@J-=y~?2~2Gfx~`p~TVW2fV8^XZGt&r(x4Za3*Wfq8-A%AO1zU}&DT-;C~^ zI@)Sg5}h`)lG*b_Gd+yv3Hv$gr|*liSBF6g^N={q`C6~gSq722DR8cWEwwb_CS{HW z)tSPmQ}4DQ0ITacTh*z3&LnVt2cdl@$v9&tuU{w6P5`>XK6u~XPKY_lnOrVZR5TMh z0NR#*oEc9(G^|h&p7(|JDV54$7~zaP_CoDI4L%-^SiZIKqVdDB(F&H;OBu^U>+m(e zj)>WXpsDn2*X~D@E>1l=3ij@Ph_h#vgo_4CLWc^|!n^4w?dA(p+zjJAi$8C_7!h$u zb#mgV8oD++Pk3owqG~V|Ms*Lj9KlA4@!Ee;>v`s(I7gG9(R!7>uA20&wOR|&5aTM7 zpVB$A0x?#Xs4=)2f86&{yK9fvq{{FgpagB5oAh+w7=dNqeSTW4- zXr%L3dL>5cB}shOrJfz%z$zgOf=k?$W_04WJ9d6#ttIc|@{Y(SX#~n4`qu6=X-o;& zTQw8pbkC{>Q~J)~+=M~+P?W}2tv|E8P2+JFhIb8^jLSrA3WQZjGb|Yj4b9vWA(rG& zk~jPD;`UcV&3OKDjd+gYukrDW{e!53zxs&j(`DNAQx*m}PCwsr+7nV9*Naq_eZ~|{ zzt3YYUPZO7O(u%bwYg6PRCtnIP5i8t?jTLP5Pus&hnkoFsc$j5(Ea2pszu#N4*(bi zt^1QDh9SJ48R5H);uWI3uq;{!%Py)AgS;08+tp9nyyr^yAO8sEl1b&F&qm>}t*P9F zuT=S$;m@c%?mAkCaZ2PM8Zj$Yrs=!_<`pjNAL&otj#iQHHD=Jvx5e>pJ0=k6Db7vJ zQ;q!y89(C%sCs#eHG5Vbrc+$k2B z(7q6FVGyRb4^)aMllq-4IPQXNZ)x}u(pXw_-*uIUt1}p8&ur`II%3?~a9nBli8Rzq zo(VW)d(zw#ZE0tAbsjkJUZL7JvU7FsTkUL@T)uiYZUffNH8g$d*T2C~gvG*L(50Uo zqTo}GcX?o0V<%m+62D>EG zW0xVYuCj@zTOaU~nh$%(co^cuH<)RC@OG!uf(ELe$Nm9cv%%_Xs+M6sygu)%>WZGJ zBpHh;rFKMj(T!K$q_@e&bv68|=%9L>X_sg0snyO15jLYRGOOBD5RWggGB-Bb-*?LG z(Dq1Dc`%#SQL7Wg83xlyM?>1!%1vUtQ@JFvZx!Va+OSUy*{D8q6jc^5;{;}d^3c@kc2yYi$JLCQ>^{gLg<65n0bT_B8Y z5IFj-7S=(^04JblcynF36b^%eJKr0%5#>u)RmR?pceLZOO^P;J82y?u?03lmDN#+Y zDs|oecDyT3>wi@A>AvewIpNAttp_4<+PetV#HYbuTOwREY)lGdeO56~c%wE zsRnZNtp`KMId{q7_08`LnJ#^`uAltxGD{R=zH5Bo=XJmk4T7qL;N)Nls*anQlX$ue{yXFexAa0Cq!M8t zH~#_7kqp}YFrFLcL7&2&xW0AuD?p&9YVdeX_^0v{1*_WmPg&}bS; zU>1m0flE*yG#xq71-tb>R&Z%rhCwiS_klrc;((lW)PC$f9&nIqMYEdbK=jD&+QQ85Zu=lIcqM!3LqJ=@)s z%)gz5hMK=@@t^%1O#rMmCK#dw;K?QmA~ZPjmLp>iT&A`CCIstW1l1j!DG(-)b@mEQ z^|?D}J-jq67B7i$w84`vq7vU7dMTyl_akKRe5n%iW-qgm*5shS_cM`qBc_6<0@Mz|QQc&f`rA=mDc0s(zp3@n)8%|7 zu(D}CeaoCd0P!9x%}($=DQn8HBN(S%01ts4a%h}MGdZ*x5^_4^orAzBh%JBYcaxHC zhg#)^>~fEQp>G!^8zBTgo5gcD_wc??=fKJ}@wcC*%}5|Xys)zP*2+ss?Hwl#&r=;Uam=vsX*bxs4%Z?Cs&-i1K_Vj9njR|_ zG;s(H^#QirWw+)(iaUk`Neu|=hb{8y#$5A{t*i8$jMVZ*hF^-str?^G5$?WfOQv zCavx9E75xtYti6iU+s!k!_@Qn)UyQss#7T!{s1k_w*BDJb-p3ML`n&y)}SLAYx+Ap zvx3raYW(9Zd4d-zPR=Bm|6b|u$3I06aKhuV5Sz2e`Y^;MYAEsWfm=RXFOL9s)NY?~ zC@Cvw?hfd6<~bD9Y3dFc3>^4)G^vG9p;kz^7z$gCI4|a)EbgyXV;v1W{Yb^=o3~ut znyKN)$mxnk?fkRmbEYr6&(d{RkHelj+>2^FuA=K*KDy*pfBGRd!TF|wbaMG4)&ujP zmQNQSA0dCiJ;@Qbw1h0mVBPP)=WIjbLsWQb;HbOz?k{{RHfdKA$giC}G-=KLWf#MY zIf(RZIpoV?GSAflzC>nR2meqdxjKG3t2DG%2G}~{hX${|5on*y8gT58GF3g$7O+@b zL}&+;6A-EB?(9d1FMBI!9Hd1@KCYTQo0tjnj#|_G!{4b9-Tf2`V~}2sSqy9XL7eZ5 zm&x0rhOW=loIPwpSRelOp+YRdp*(vn&!o{c?7O^f zZOryJ*C}mof?${)_`Z_lox_9HyNp^y9y-7M*HzQ)@lT%Z9)32sT3=TD$4>Z@FX#^k zFSl4#`m-30ui|iCeUcq>wrg#mtC2FECSnpDan>IXnvWOrWnX>G+xPQv*|jQ8aI|q^ zwyl~^<-NM*kFp_uJfgZJsx{pTa%+MPBBh)Uso1NNDwQaiw3k zO2lq?De&sL=jz5k5FGFIvkl;{4Ck>3JhwI4P!&JloiNXfw4#3~Ws(DK9)%t*JzahL zz=bdsyS(#vlm|%d1EVSg?>)QPG3IB^1Au}`&OD~56 zzvM&;=!;M08I@6?ct0zya>Qqo_Qx`#`(CgxPv5>9dN=rTlWFKttIWR#R6WR<=d(ivp;^TK!7vrRuDg!C~(^Bok!#JK>OR$xw=&M z>~9cEIqdeG;|Ii)-~14Kpg=gq@hTcTSxI%>imw;?Ef`T+!Vbt6 z^EDw{`*s#@BL8hIOYr@sBY_9CYne5NBOSk&to<$;sz^(WJoNx{X)@PrvI(cUKEkCm zR-QI-Xlvp@@pX}{#iaY>`?ihm?pxT`JURHNJdG9^x3yTlI9z%-{&u6|i#yeJJ*P_o zAN+<1tCW_?KZl;|seqP%b}zsDmgPHaZ?kyqbs5Tey8!wNf)&R;AzfPG7*XbaO!(qJgqAq00eL=tm5d zLlYw3qsVFip9hEh{vxkCYG}!LE6j^23Gg$~ueCC8t&kI^8J5m_f3<<%T=Y<#%`~+O z{}DRb*sj(g!YVddwAXB|v#WfjqRrgc&|Q$|$(-y^eNq|6gd3G=mjCs*=nz1F z_ft=nmSMrwaT6Xtww?S%OMZ%6k4Aa3HVQ;#W+A@3bnr#&yy_M9%ZBYjDXUQElgv9% zyNioG#^oNM=f1U@RGG<*)9(uGo1Krr+KOtQak8ApEuOld%ePQsPzj#9o#%bFKh;otnf{S8lNm11#Y0wuZDM z71qV8l=vAt3^^>OX%`W@p}Kpk^)~pZ%=>5op6^y=Eu9>#Sx&CH5btu~kULzv@kSV{ z@x|yW6~YELgZnxQM)`b||JC_#PZBWLvnCPuY}Z+84o_cIyz)PNY>uB9U=MPwvZokj zv~0*N5bvLuQCh9I_HpXO;l4+Mr$!5#+)tH3l?M3g3SXR4iW$k@7GLzUda8DInS94M z|E$YX9Z6>uy`kFYyZ7pVoBo1Vjt+sGNY3P*S6@dO9kpXvT_~RRChM6w*~q5KV}W1K zQwQ2+^f~jW$6k$jnNPmtcmMVH*1JW?i>i>bX1Dy%)7aO=nVdJB==vC>=B zz%W_`Ukcj-A(p5DnB=;*x+!)xqkQV(H{*XT2h@A%5h7se{)bi){G}x zWTp!}Y&34_IW`orDsWqJ<>;9Eg0I_c-sG!*V-DvPXU#c-@Zb^Y4rp#xnNNk3@}uNK z2OinC9^x7rMzVaXy}zG+UJ`TaUaY7@EAkxv`wc#Ulr3oT{!Q!ch}qpW`J=C3BZt?m zw&!ifzD@ink=gLIZH`GEE55XJX)krG_{FveMDnh zMgeaLNl%CtH0N0E-O{)-#xUO z_k1_$xu))y(vz=5T=PS337@N~-9Dwbbg4x@@45b6;U9%!FCf3|=+#Px2N`QL=i!2C z)9Y(KA55DcrTQIi^gDm5e>VZJvMm`NxzAJh$o!cNrO6H5 zS;03P_W}Ilg?s@uLLN2GHX}qs(hvWT_gpfsVK=EU z*`ww{^Ua~!(F1?T)t>;rx5{u1E-o&>?JXw{C*aeT8}NI}0q4S~!SD!fI}J2NSVSfS zgj0`6t!@3s?JYMarvc|uO^HKuUe8wKk%e8|ttgx3bc-$PBMVZxefdK*DOa6bs^1AzW*&U z5C(_=#DSMGD#v%7LRFuFL8f8EHezaM7Jo zQE8v>-Xzxtp)P2(EqAbd z=X|AV^YF#I)@YH)K-r!IYSTrHDNh46Az(C7``?_ZbEvR1Dw{h{HW96X-a}Co_-*WR zf>A~|imt_3bn(1>3l^`F7f3{Y8tPwlS-|3_m_B+^DpYEc;t!al)+5v6M)@>L*oeRd zEp+2Y=a@VmTJ%{iGlv0l&RFzu7)1?>O6iBqH$C)J(;?bu3mIE^F@0j&uj}sacZBBj zmYE{E=pOPI1~5K%&pc6KFf)om?frT^6yTee8laXQT+XM`l%1zi81ykVpF;t3JD^^v zGTJmYAWWrl0gEF!U1>KAq%?-KDCrHe$)xDi_5!uFUppC7%pOGCeH<0R{0VbjL1^pw zA)(U;zpEr+TAS+=jge@u^?(ryw$p48Uq=G(TY_K8NWsas}NajZ;iBh&=UG} zy1A4 zP0wtif7MA*ZT+olPCpOM3^f&Z&9U)qXe$`ZHv8+0z*ArcW#@iwqkd^)XrIsM-yCJ1 z8+u9Btb5keYU<)H&tn!+-QLmD?%qQQv~v*bb!@_+j{rB1AGwZq8BW!<7#NYH4Z1HZ zFLn-H3rK%0kD}<{#$S|URpR3wGkAS&gJ+&nD(0idFszi}jb-_VZpD9d;Kr=_UhXyG zTnFVcIzYUSX^x{oJ4G{ubBdI%akuz+Dxp?SO3+5Kc#+?})okL;s>`;i5MMU1W;dS_ z9djpvBiM2hWgossja@i{CbAFXdtpn8e%fA1)t16^w7z`f#XN!QG9Suy1J%HiZDLmO zkOcKq8DYvq26qU(cAP6pzy^~Su#Ayl8}SR-*bF3>Gz0Yy+Kp$Lr^F)7WzARrs*-8r z5jUiLcM-iqe>2V~j!mH@c%wiTxa9MLPRPI$4WsU7f;0#HO=2pqos|8o#b-s8ukEuN zMjwD`RdRxag?ua6Xp7*qJ<>);3TaF#{w;!bfy(APVX2xwTR$R!fH4cc@Lv%M&NB5{ z=Flo>tSt;!=4m3y71jniCukccvSJ~qP^2hAqF2%`nvD|&4$T3`y6ve5BG^x=i-YxeWuwV7!2=Q?ISbHrA*l-~ zyPQE~jiQChcbN%coetd`wXeb#qsN*M1F5vB7w_?H0mL?Y1#YVZm4M+UnN1ZNjD!)v zBd+19;M(T*NW)sd$RlKSRp9>Qx4f@U#Zg0K&WVUL7Y8_#DaEg?C+HLJQT(XLavTt( z`c6JciGlvI#r|Iix#bG3NvjJwW_(~&0Buhk?bRTXmv|sBOFV$A?wp{KByL>FQ^T{G zLm;Bu9PxgD({BpemI#&huQ4V_mIGgna_u(3lty|ksR^`%K}5K7smx`T&|h~iNgO7 zuieQgS>S-H1QVQ)HWS>u!WV*GLa_cODORn>NO#~6q5b*U%%F=&${ldJYsHMVdQSFb zx*ctQhKr^BgqTmQ;v|f;dJtk+FedM~zwvS0SUg+Z8D_YnCD{%-E=dlL?}cZypGkoB zUi12K5OEGwlforV5lsOHc{=j8SS85>ue+hS+>Sr+NMt1zOxH@Zfz2?iHV_CEmIoZx zkx%PIFoIziJ_#Xn==4~oujL4rBaLcgtN^BeYH#co(AN~64NFy79*3y@IczPre!>2X z6o=QUumJGVxhgfb1FfYl|8s2Em?Asm8y89G-DSOdK?t^zPpUMXtDL9CaB+n%(n1lP zW>zt+`-9A#3ZbJ{%ns^Us$72=w-O6AwR;HmcZu|3wUDFdxG=pvKb>Zs^H0WOTj zR|L1C=n_1gJl0Kf5c4@tJEkDF@Lkpq9oLYkK%#lUVd*j0PkgtYdOP*=pnA0RY>hib zk$B>@PnfAu^>ig0t>KNd1Mrq{Ud4N-8M0@1L&w-c4jo1=5cdNv)_6~ex>OxV?>S;? zZ}3sI22M9{kZYl=(wM5RSWp8P_S;>M4C*m1i%{o>jcx_e+Y&5E6W|WvR)4p8<&b8a zkuC7HPyfAHJwe@ILp=$UX=xVq7$dP<@hD7?jpr8gLBxtvY60anIUKSHWfy{`FK5n+?xc&KMaO|Mi>hLyKI>KwCleRvM zsV;I5f@(V%8`-KU+$nE!OVW}GmCLVzSPC~%N7$6sYoFxPeRa&H`Sjo!M7EnI)jl@P zouJKG9m5NM&{U}-5TuP2&U{e9lbwp=M&O#fR5<60Dn6}1QsmWBAA?s3abA;w!gJW0}3_YC`J5o7M z@<_m98lP(Yx(rdd*48Db-#%OLo2UfSO78OK$TF3PFxBj!7DQ2o#SJ?HOJ$|+8eboh~GA2Q?MykST|UipvNRmNWhtD zEtXInb4=|CpP;b)QGfTIW{s~I+CpfWjh~Or#MH8nr3u6kyXoaBG?CFK-_!X5pPFxU zi2sGypi$@%=tdlgw{2npCGb4h@!|+-94cat=L__T1FRqLCa@Kw%t+|bQxspk2MRw` zoJR7*jEYhxVgNO@&OZX3)x#f(isums$|)a0#3)F}`oRa$#xenEEeZsez6ow~#CXxB zHl{L`N84ER6P*l020fu9_@`I%ftFXDrOU?c@~B$)5*njy^;qHrQNd zJas4UXn$#|Fe!(w=?S#jd@|@he}FgmwE{msbeSdNHr0g8s1EcGP0i7)1J_b>kT#~` zT*g~Lq8yj36AkkcDjx4NYEWY(5Tk!2}x`+Hh+a zvu{XrN5pYG*k01na~*$CDNTLPX$FzR<^1%MB)p*w#fx5t*Vf+VF67O-s_M4V0SNg)F-3fvT47(pe_7EW>Z z3(mqi_yvXOibtKG(J}KShxCkiB|(K)BW-Pbr!Yi4AyBuGDHpM?=jI=$IU+Bj@M1)t zq?%JcR1BDgG}cy0sG!9l{aqmHh{*dq4`>Mx9!)%^I>w0W@&d2LHEPo`)|Ss$pOkD; zQ}jCMBwHnpj|eXtppqP|o#wAXW=2H0w6vQD3Vddu6u*CVfNz^0H~3L- zFDbFPhs1ycB?L4CRkmM(z~ubU=VSw6`Ll}mPmX7^G3r`Vh3e@%Owk)R#p5)s;22qr z7Mp>blNVCT2Te@8k;`4d#%LHSg{IGP%o8N&q}>Zs)(qZA86}VY;VlprWQcrD6Fx)H z>@!2?WHb=S+vJVirzSSmI!4Vs?-i-ck#1YJ0cNM{1IPxuHJDoJ)_Qd1Ed3a@3{6nh1u`df^V++{XMySB zV6+I^&>BBc#Ma7-Ae{B8TuvcrsMMVmk&{)q{pAn8Dei81*?S3Y?MN%c<_@jvgkTye zk5?RtpG6sqk`AcLX*Rhv!G@>v6eQLg!f;I zdu#~l#$z!l6AG~CPTc6wZezCP2PGx-=#)RrfTb^2|2>_D)e1&W6}_l)?uOa5ei92D z*3|9=CsoWSaj&bO0RNT1Jc_$uhPY4t{>KDw^Lbsf%v5MhYmSIdV%b;qzpb z6{9IShRj4w1fYUtG_+Ii{+7_JlocdWhqHRm#E02=GNmjmh!C~Ta=&pL?S-Vsww0r1 z4AWV7)2c@$(}~C@SvN3t`I>7W`7>h}T0IqbF7uf+a z2T1M*)aah*u6{|19&f=iy3QoVJYAXUR#qUPP)_ir!s1Nfo=JS7P<9|Bz~>pH0UYFA zDsJ;W>R0rhQB=m=agZqcfWAGww22>L>B01p5FSJlm7sFy@|XsQhtR!QY=;rJR`}6{ zfV3YFcqX5+kE$=acI>Rgu8RAbKZ5~T7{5<)0Eyf}MF@t_Pla zy>@66Pr4GDn}QNxw0gd-SB7MapwUtf5YF*@DjqzF3eK(6dzYIk4Bq^JN0*;Yt1PM_1H;9R~VHkq3a)BPf@*K?8g)>yV7Z9(Xqx6LBU`ww_IZdmDAtj&#^AK#y5BiA!EU6 z8%gaz0=Oz*K{M%ueOfz(1Bbs9JxZeDZNRDyWx?%@YYewCRuZP(38Trzw;DV1TSlkF z#y!cPWaB>r^puN-0!*E=!Lu>Va^(sns}vHHzOjrJ>FRn59tfaNcxqFb+yySDhCQ`f zQigA}izKy4N~OW%Yv$CT8FGYiwuRQ%8ZiPFOOKElVg|_|Iwa>7*~VVkU{zNLE!#hZ zNnsJ0e1SHpf0c+#QIr6QjSaLml%vE$Xm}*yD1uo~H{6=BLd?~LWuw5XY}uguh~Z7#Qxjd*A;haVUOEm7~vI{G+=BIM@pru!>3qQ3mK?YNA^`Vwk z+GjO$SRMialf0oVO*U*xm3ol=kZ@B;v-8JbXP3tifv&bmA1q+DOL2@e1FoitIA!pw zk0vFC^21Yxks0c@x&sZ_!Q_*7AfswZNDKQ!^;k|G6h&4Yke%)DjsuRWHlP@~<4mhc z0Lt1MjQ@cGKO~xKY7E|u)wN|3LqTG?@eLkc@H& zb(BYeUxnVphMAB@q-kEjg#u~Aa|&EgdN}4p z9;ebyJ){@VNhijvOYaBVg@GrevXtoh`hyK9X6j%Q7vJ9;^y{p4uY+oW4A`bSF;KUp zJ0Lb?9E|48vuWjF9o)ko1a;wZ!h-Bsw<94e85g9tol~!KDw8V|?r~JvHDO1@+42kx z*Ct`ihsRdonHGo5nSuhIauf&^PFvWK=POcgN7=H*WNq)ws@`e`tHf6#QuzdmyNnan zjV_iyB&85(Qj?-nh8Pg6(|Dvp4GW9Ks2kzl5u?YQKUZ*^L2U~Q6WCRMS-vNrDQc8>4oP8kN1(>?Ox4X zhswV3S$~j^ja#?Udg9H0?WR7Y-x?MS7Ek6+e%OsPczAB|(C_0> zS1#CoSI%E^IvM#d^u@5KqW*80>%CiZyRY&mopxUp?uP!c{iOdz{{^j= z_i>bM?B@XR?PlyFGx={iKI>HPW)f3YR#R)2B`_F>n6h4^54k5S z?0Q$0wR$fWNOa&FsI+pZO(Sd{yJplz~*AF@={@LbabxJ zo_1U=EoWZZO@FX2{ zNxzLO_?yFw=p!y`w&lf>e+5W;FDBv3t9h=idvGs%Fk)Wgd#=sXK+!O#0lc^A7vZ!v zgYk2J_6~1TNr_hIa{FC!n;74jl7QTmv^_YD)!{TqK@JQ;py!?5pMbIwfeO0b=mQ~O zmCUdkLRXbWg_NM`X^ir5ZY87{OYg%Nb1>Btv7N#aGs4za)g|ltZtGCv{kHXMIO5!SM zcxj0yYh3J=?*mkVAx2l1M#G4qAY&V$IT@(vBob41}kwcy$_QLAo+n=TNZC zBshVJ0IN>xVnA`-@E}QBR;l>O{@(d(my9gzm%iF9L4L0pzFw5qR>F>cwg*!&2Nhms z7GyNibqf^f1>qTw!8pSJKOO7DtA@W&R`qm5h8RPhMpJy>D?JDx@tBl)mI#XE9D{`W zm?`N?UFop|{5^iV&_@py?iJ3EJ*}%(m*LE8#E}(giU{eyImpB9M#gbn zEds{bHuiSs#fI8jgKTlLBCTTsEgOCDW~}X(UpK4P1|}m7z}%E9^UPJin^Cilw{uQO z8+zsSa}l&Z`L$l=15ND#ECLrhGBpS9F#zMdxOWNimA!sxq_Ie4+BO&2piG+Z`C;c{ zq>Xw8mr{fdA9k8~*)Y(#(Y&KmH_P2o*39^;5-z_@AAa72n+(2wjua7Xrpm*Ky_;Ub65*$2e$RVuZA$YjaCqv02=G|Ll~} z`Z<(^R6<0900TB=Zm0x{(bE$t=`+FxBnX6d3o*YpVbBqIIe7`agQ7jcPpsox&3ITp zwehLNb;q23i->wc#=GSyTHMGcOclEkK2ImH-;oF=L#SiT7gnkYW^ zi*4$4f=4KIt#ftjj_{l-;v!~81bKDxr1~(yzsoNWT`tvKB7or=39%3>rx6VUN4YEmxW-z1<%&otxxBIJEChI};LCxLysqwH3hNYp5oNtgH2U2~XL)+GV)^v?gA2WOJTLI-G^^(tON~!O%V0f-)DcYfTyB`fWl~?U z;mNqpQ=oXn+Q1;v^!A@&OAS^^;;A)!FW{acAV(3yC@Y$5&IL9V(uVJ5<5Sr180Ds` zy_5_^8fBHKct-IluZ8x&;8l0zfQ@irh4H*R&wdd@z+#XtkcRGj`jA^ab2X7bYO~9Q zYG4}OQd$S}VR=7R2JYsRr1~o9^Ye$(BMWuZ+p=f|z0CPSafs1HRw_mYH7eF+_zp|} zy3KEHG00N;YK{~<5uxn~m|NxK8X^8Pk&`dNWj$qUZ9P4}J;rmqVYXtc#y`r!44|x3 zz4{q1v5d}kO^sgv&1}fm2~QpIZ^cesgdKog2QO`12w;*#wrMP(8EbC&(+1!EfMe5U zJ)3GA9%}~<7jo;abE4bqF?>4K5wvn$PM)2*u6SZ1CsdHZmE6e!Z93=P@^e4ndOSpi zn}AfQo{P@ydqB4`d!&?!0oZTW&!1!XO6biYT!`S_*0@S+rNqlNGB03~0jtH8pj&$X z;^9^b$9qdVr+8{sek+DN5b>2t0F1j$Mi9+@42PARUQsw@N0P!ToKXisWPybo-b2iq z!~FfH{8HtG2UF@r=BJ zEIx0ctE~-3X807JpPthzWJ;zgP0Ns_VVi6nT7P-e(d6pI11&^EjLH)pT>JS0Z5XQH zhwgy{2ltzf&`b&%6`lvD>VSJQUgmP8^`N}P(H%}peez5Mir}{L+8@R(!~@&9Tqs)o zWH<$7jvupSF}{}uK6P|s^KShMo98%BcUF`^HOXceQ>l}V*)e}CuiQ*>C_ z>JnblfX-GIE|}ott8$|^nFeQ1)uqRQm6Y^JjMO;e66q>*#A!FKa+u%;m%c@CdO8S-Da6Jd4TC36^%Z0se z-6-NB%qIiA*u6i*yh*rN8r_L5to)gv{F^BK$DIp-OG0@{xt!+dS9w-YP$!{K2_-mq zFoj8m+D6(DiZFJt6GKmKGo!7JkdkF+E}>D0ccULWuFF?wMWseF%l9=ak_L?fchwst zBF{_eF+~nC^D1xqz7YNZGdq=j0&dhLr?o$wQY%MD;qUG~yVv45@A zYA&lhW26NO{X-GGhVS9*@)3l9QKm7kUkJ0+F_rTXTBr<9H9hsIk~Ob*<=+(Q@LI&; z_1H6_lNL}`E}XV64rN*y1@y`KqQu~oSg=j~AZ;!!RXb46T|%N(7wU`j%ek8e zu5ru8G3CYPIAy(*6_80Te<8%Oi5_;UNWyuPa3U|1wH{Gl$3*`Qa(XD12M_to6RR{G z-RjmjLw4H8+fs5z5(iWZe~y4LKilgFSa`}GD^SY`@hY+g-8G(-g?;D(iYi#67fyYp z`6F`)u+%|Lh&F{&vyDQRs4_+LwFk=n|CK zA64aoPJ#Grs3nZ7ksP8A7m7#Iz_3{exK>t)HK)K&UPYibc;&gc0P$}UdaG0kN%^We zjhxebP4iS6gn+w<-~jd&h?nv_&9@vn2eeMHHWn1T(woRV*wv=Y=9Qlc%!c$i{ltwI z%qkdWz}29Zl1NH?;AI^OCs?@v?WKkH5Q2p3%3K=BJrs-WBaJZ1?ZvS^hKddp09z?T z4duqGR+QXphiIWoti)n5)AAZ@K1d$i$t2z+8kn#asmzkO3{s*12lL%IWE(O<&Wjdr z6K~{7J_e(#T0-S64|~l4i>~={V9~KirNunF`>k16kY9}nGwlp=k%Lhqw3>{Ygz1y< zwm2G%$#~A9lkGf#cUA~kW$Ll3L>>vkU_BQ5otkTTYN}uk6r2qf(ghxZwgXh1hsCE6 zsXQk@i*Q;y(Hv%J(Zxnjl}K6mn;PNBW~6s5Iik;7iCS}fT?VvLDBpeM0{=pu46-`g zSP{SYGp<-kmFEN)_Ff!}>noLCOzuGdO;ZKqd6}6;+q=S)HO7q*=_N5qu2w~Y2WH&4 z?Nv&G|JV@md8maE21PXQ%$#x|CW`<|>Y~u$J*L7cG1-O?wgq$*7{ErCVJo=;QgGI} z>FGHnpg4~lB+6pALLKQ|SZXk}T`fUK_ZySmRs~+9##YT4%yx&Xi+k#%I5DLzA8;FM zCs@LJ6WT~@?zY)j>@}~Zty1iq7%lgd7WU*O1sTQk_YeYbN+>2;gxM`6^dQDksacp= zG#6N}S=%t>HUeE=H>DU!^~?j=^)V1JjsI;Ndo3 z1=kAm9^$aPWM0K^sop#@`?^q4wT)Y0$CKgh2k0(y#>;KqGF(=h4EdV*(wFDKB&4a1 zVFK~GhHD|99{YB{+$c;~kFqNncFg`^^%Jqhv3lA#7pIOX>3W9h+1DCOjC6)nC8Q&! zeP3q+gDOy(IbFI*m`a2dR23t zbpuRHNLD3KxAy%(bR%LV?H)HRZ=r(5#Tz%L8U0?l{z-4& z%@iXih%OHw)|i@8j8Cd;>oVd$2grqBNE^ul%4p2DrLHFT-GYkh)rqC!vNIxzx@9O3 zlrdjM3%5M%Jqs3nHKOp*pB`I>QWm-kkLh+RL3x{=NrGQgF_+gg58AY%=3OT55wOz` zeU#GNd<%sW!_4-N>LXE@k|MbMN%;O>-%f8e+U|_pXavHGBU>XkssZ==nnng+DpW`A zug&iQjxLjji`SKQbqyRU|K`Xj-MVw5`d`?_8`bsztmQfu09~)zjd)pNP-0ME;94_! zf9r5Ek&k_D7&zLdf3A8TGXM=_Td>0LG|D0zrg=f3~>!s=}KPy<7)g*S^p(p3~&hlC&A;+tl7p+jf}yg|M^Eh>90FdovK~`;r;*E z`A?Dl%iaG)|I5yQs^b5mWoo@`(mRX)ttX&jfNPpUJ*zSy|NO`T1+fU#xq%06^Y!F! z=Dm}$AMIfq%tYwuz0h<_7W7f%8+!XcO#l@9kI+9YwO>MjF`)cFtL=yXw`u+_`k%J= zZ^Qk!Y5teB{mS@H>;E5~)&FMPhdiv4HIs3e`6o(}`qzqfgoqD&D5yrnHnQh=)#fS1 zl_xJ%%YW*AG>GnxJ(SmxqdAp!%i#FE)vYM_yOD;M{kMmcTPtgl4Q{^qb6wAAkMB?( z7&|qb-;pLg)t^*nHqrA!dg|AWbG9?VVKVD0YtuD1YYsvhHZ?Y%MG9}~^=A%s+?=*w zEe!6zZOhY_bL^yE@ZTKV{@-m?m7*$d_gx7e{0C^0>1w{Cb8q0)Cho6W*G}JEQZDvQ z+u8H!UD#1?mfJf@Oqi~LzR?ZtcE#>EGcW#==eBKYa(ii%Ex)#6M-p~AU(i4y$i*n^ zHr^>Y#(DEOKzj2`nf6p^(XTdFu@L)L^8uS`oA6d8rfst2MWDo@QL(lndk6J&nxCJZ zN^Z^X{Ib)WpJ@&7YShrM7eApZdwu zh5u=M=_=+@`YybB?DywL_4PGbUC-x^FATT=097t+>6?v}#(SPCq~s2OeLejoMm~Qy zWzXmM+}gQz89n|R&=p%~_$moE0JGH+kqU2e~gJ8t@TA6oJn{|!%9n#;wh%%FW7KkOw~@!jN!Os@Hp z0~US>J&a2t*iB^HdYG)-NF`al@ZE^~K~0H|k_~|COyDsVU#HKXem%)oEl(Crp1LF3 zxF@0Ode}kBqx5d@pq1xMe`r77wtB_zqDKZw;ct%la{Gg?O`Sd;l{j#0HDZ_01+I}C1W{tPiuRn7ti#X!7EVoJ3lA+b(0U^?x(z9-?;D}-PBil@HPo7 zbT{x*i2L=X%GMS1?U{AVTjlgUvZTej{3Wq?d3_7Qt6WCth|AHkjnGB0D&xYpvt$b`?KZ7zrp^HM)OlwUXPbO{GDpnhYipFa$Lp+aYY*&N}bMr>g9g9^q{8 zBi6J1x7O3n8%_CA2EsrsI_Cs$x^0H1BM55mi zI~MWRt>T|LFI9^gwf$b1eYTx47HL@ULsM1doQ->KjCS${oUWAa^}{9IJZwu4`l;8b zbNHCevB0*>JI5vZTrH$M0!MU|+_r>7OBRzG6M8q1tE;EHUsok=-k7Xvzax-Fc<}h- zVMbww+@|fL13hxqS5`d!A@}oZ2Ngvqy&ti{KB-u#)y%sj=7pUxy!G=!5I(KXbqlV3 zO(jFjb35DiyL#*1pYAIqgu^uz1WzQ?V6zG~t263kaGl%3SR z2X-7b%?|UJ>uI-7{VjU7GfCxqH|l!(qF$pMlRWqev}1k-{s*=R$myaPS55Glz>4?B zH^;?=M`M_BV?BPYmL?_T0vALbVxceJx^-1G^kjHtZrL`!ynG39y()0Yqibnbxij-t z;fJhg>nHv}mNsqI+q=vr0)CS^o}xVe<_Lag_e;LQGr)d|^1x*Z4|3H;ry0Zm64JzX z$2^b!vQ_L$pu#(DaTRqe$eb^YLw!>R0-RG*qFb4{eegQh|oGBI9*qBBF=_?#n;!o zx+=k;^eJ3Un*Z{92f_b6iGbujCfI@Ky(HzMZ%|&VTR>Y`7?~IABC%icxEo zaN%dwCE3_)U~FFcS$cw$CRa^Fd>flMlu=UjNBz>uvF7IoU!7Qx7tlT&5Hz zjkPp`Y6e+bw7NK~EGybO%F09U>Dwmgj#u3F#Vg>ZJp(1n?Jp}Q@{h;1$@>kLLQ8MN zk&Szh4@#HyT}$jz){&dvg2G*@Bhy zz$T{F!<&a4o~!$xh9{N19&)-yjQHt`Hx25!X>eJwbSJ^VH!rTVh~uT!i$uAO< z>gRX~ZxpxEUxL0)tkQ>~tcL77Eeg706)VQsaIx@g z6O&J?GFu;;zFce0$QPsiK8Bf-E3vQ^>sLcIy>l4vS~_vzLydb$u>GyHsC#D^lvgGY zNr;Jgx@G?_k+e(l8lj_4D&8#E7#AHnDm1P?R$^LGsjuVx|MB$JaZ!EGA21&U1*A(F zX;?sHX(VLHU3ytsQ0Z=vQk3r4UAkPBT2fMC0Z}@nLj)uw1VNS%l<>KJf6w#$vv6Pc zo_o&BId{&?oSFBeSD_v!kBBDY`x~Cnwb}ile@%bWp!uDZZ$a-&ypSOp#%~!Vr@@VJ z$F)ndZ`wrPO(GzkM3a!y>78r_JJXSov$rg%QZ;UDzfo2QgUI86x6pne}=Mn{%O+b-(jo zAHH;b&jl&_C!v=4P_U;iQYLRaa`g8LGD33`a*&OmkzawDYboB)RoL6$S#d=THbTon zx>>}7oOQ)t1V=Sve}}2$z=-QPOK;K|#*gd373zNo;LwO;+A4V4h0FLE9!grN-kq7L zE$Ba0D=+a7gQf9D0k!t#V$ngU^I6`We`K-C^UDdNWSV!CWmCxMoATM|aIQEdezWMQ z8I;agv4`r08erIcS8AAE)im$7)@w(Ox6GXwfRZlUgkJ|7ZWtfd zHKomKTyrAw(S}7}G$JXPl;6ttP3pU%p%d`8V+nM3HdLOT7h>)4-O1pN@k}`_M5}z7 z0Qu%eXAxczwJ3W%zf39P4}ubh>r>huagSj65$Y3qtaOM}PSQhxpnG+kCFxB61Q-RQ z@)p(86NQtO9Y#tEA>aI8d`KUhvHO>)?FbJ9d;*d99g^JPY&bP#dZ?LNFQsrt8y{3Z zqa*uHdI`GdT^5wz^+d^i3-q!a*RSxo(HP3_jpx8QqW~pOTTr){`rS3}?RQt|M-95k zVk*h(bO}g*dphIDwVrFCX2v{5SM$d* zj3dYaD0%#xXW_UWH@qGGPF2wb*%B>{M?-u*J_u0xQBy^6;mXM((e>>bVJY`5@s#f$ zS#;?c{nM^KYBd#V>QpP4dK=wT?+W?GO~H_uoOcTLP@!7Xt)6oRiA_HVYzR6`%ry?= zX*dMd9nbhIY>M{O(4JK$Pi3jZ)A8IkyLG86pDUR)`lEGiu_&qi+}P7=WXB+uVkU7s zmV~{ShpY)(^WD`4)>45x^R63sQxeHAY@3b>ITZ*J>G|b*vN?aHDebGdeRK0$*ZN4+ zS`9f)7E~LNy7lf$_&9{oG$#F4MbM7P(8V)V*vn{>@(!5>4y-B+?LrZw0l%xlSnes* zv{K0Ml25@h_MhhdM#l<)v(jQ97!aQ$@CB6g%1b6`Lm~@mOoha*cBowC(flV@Y)9eK z5utQV+-YioeQk!L;E#=Rnl5sU9ZAftN9WF>v9jN4M9Vqz8CG|o$ueUG z&7;vpp1oipD77F(HF-=j<5HY^Wa&2~IoYD`goQzw)>lq>kgT)N*f_+*Y)ucT&L}X{ z#R;%cmBi~-SD|tj^+m!pCF#sL4HT{$xOr=rZ&1ZOU2Ku6Tt|mV+8kZS7$g(-4e#@K zthMf#v*h42$6)x37v_cmPMY!Q;t`H=NKC9`<0FnD!xO6~z#r+mQb0Op7K5iq+#DGa zRG^hyo9>yZ;Vjge`{B1Zb`cY$J5z&U?BiJzb?f#rsU|zS2rRNxPT+vx%*(62dh+ef z=4SE>P)omym1blga;y!=n|euc`ax5?lvK$0{jOXX&3E_QnWZw*5@$6H4da<^O|uVK zU)nk4+5jbvHv#%2>iX-!j@VaaRvX*=e+F{ITbI7Ob(CH0|Z?|iW=KeV9HVACJk>f1cgZ*1|)-l1Yek1m<4sUZ`i zPyJ5s)~qDbrZh~(Y@nPXOf0cKvYC>k%ou>%Q4Db|Ao`-($9o9UXcqY}`UK7@umtqh^Ha^U8>epDu~Lx{gwatYw)6tY z@fQ`P|R$KT!)=#Z!jCDCtovyAagG)8FqPJ^T1 zyV+7Q#YdJHpS z&MxIe98%o2^vgaN4n>e-fsKLkSErXUqY(s*;MD$db938#H38cw2+3#yDyQ31^%Z296SuG{;S&uX8A=>wO=s*#%JiOM#5 zoHg3R0(KaPK0f;}gL5hBKz=DnFvbV7B_o6-^_DZOO|^pdRK zhG$EiBS(w4*Rg4c%g63rrOa-er#8wBC#@S}oQi96)kPuMg&1(shueqkD|*9G4XiR# zq(XYmt^ljlFmesutrHQIN2+SR=>`Ws?bqJd!?YrO<1MKW#bQeFSxp%i7H#5inM&ri znTY};X^kM)xF(-j?_Ikyt7TdN_%=qLMW1BvRF%%`HGQQZm+mi+u`nn#K1*DATk=vx zqN{b0$A}{P3fh}lO4W_!%esQeE70;k#>|N(z-LW_M!S9?e&qK@%gbuWT>D10Tu?JcPm*(* z7lkQ47b>FyDJC-zU3i+t8JFQmcrqAQjNqv#1vgVXM&y|h1mEL`X@n*9++3%Qq&@-U zD=h|gS-0-2Uxl$6WpG`s!06(WYuJXlNA8{oT~d;*PEAYn@O9{5K>jST?ph2;fas(`V#J7Okh_ zeXTt;Cz^E4n!{bD?74itsp;@((~ote>!AjgEi-_h&TeKvs8&FBVlOybx=90*%ZgPW zBWHuIrLbH=)oEZgO+r5k)+Yd)U*~=OlXtHTC|DVrX>su8cU7v2r0Rao{K|!84;gB) z17ZsFDN$8gwh3a!fl@x8}B$*Xurk` z>g}TDq)<(>%954@M1k$?Um`5LEGGJaZcOfP%cQx*FL1qZP#5v8i_2;$;G4vVz@MYk z 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�o6z^Q++bJl^Z4jJTNVl-jO<$#j9vBLWnvrh8+vu&vlPrYx`6* z2<4C+lzEq%w@lg0%~q`IXZ_9dzpL_ATTP#_8F#xuhOXnG#2&`YvNhD8IGO&nhK+ab zs~P-H>FU7?EfVD$IRh%ZjnVp)wANyCM-qLtW8Cr29JuWwq7XE^IKA2U#@S|+jS*D% zT<;b|6n#hmc=`yIXw#fTpSGSs| z->Uk90yKcxsP1sHw7lyLrf;i>f4C9Id`@GbiHJ z1amkLZdfa_w0GNjac^|^aEsv^|6t+?j+mM;6CP&3rw~GYBeVk{jQK?Y{WqJ0Br#lC zC1tSI&h=~^(bI-15@AKWon2v+cc&KBaq)bq4w*L>?&vG|5LOYoz2i3A9(?%1Cqfb; zg9-Em@Iy3 za$t|-bW|YDGQbdK8L&7fGE@EGc~+gw)sy3T+S3j9->AC9XwrkmDN8qtV2{jL_3Nkq zsqM&-US}y+(_Py*h570d%d(!$A#-*f3+6&r(dD;sot#0cTX=pJ8+GvbVl*zz2gseH~QJDDn;U0 zGMwn^+s`d}8y)1&(p~DeE$*AuiYpAsL+NW~`xQgUlQhHFH#n^Y^Scv5_AbzBtwDY% zd4+|?+JzB>FGu!-&m9BN83il*zIMjN}$mtJm(*4+Q<(q6=ldRbN(%ZWHIlO7FfG8r z(<0&P-GFs8g*UAzfj_{oJ@DDE{}Q2P|E!qx=WN789GG8vb=$iP?urO!f1=!p?ISQnpi7c<*SL<>Yf%Xn6>8v^_u#_ts@EMi+aa;(4 zvTFaY6T>gkkYbK%JZB{^l`yYQD+aKXl{8%v3vTA$84;GxF8o|yi{TkGuw|QL%8)R| z;M+BsypMx_TboaYg{I}!Hsb~(to`||Mmea7xIPc*djcf?dG#4nYr?eBh75h!6*)sb z3kvu9xZbFrhV~2Kzx&VDPSLO1Xy2gqApfQu&g*$a&VpG6q#wAQku8pd=4!x`YTTye z8HWLr(leKO(2v(IC9yCzuOtN+IntNNuk9=kNQxWsNx9BCvp_pZ3jd3fkb)5}J%*$2;v&BqCR zCm6h4x(y#sNftWsT(})whRrrRD-qFrt4E%fxffvDS(yQ{l~=O8ZqeZx23^nk5WA>5 zt*L5%|7NW}BJ5pq3PJCmS6t#hAHW#PWy;{KHlcFA&Iyu-`m};%u8*^6YP*#Sq=2u2leD^<)=@SO)WvRFv z;Vf@_5^VrL(KI4tu zxfMpOcJSasA!DUYfcL{oJWb|^{u|jp;t@V`A6#W0xU!=;yDetRfSMs`;N$X5&&?Kw ztiHE3w58n@`!YnKpj_4X=xjk_d|YGH;XjgsqS_s22;)UdvJi%=Jak3tO_+;~r30

Lcd(%^l!fvBb7=aWD^mO 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 d_GSkzN-B6a?ZUEnc$6!F;~G)O`C { + it.todo('Task 61: completion notification fires after the mission status update'); +}); diff --git a/mission-authoring/src/__tests__/integration/missions.api.test.ts b/mission-authoring/src/__tests__/integration/missions.api.test.ts new file mode 100644 index 0000000..489d29a --- /dev/null +++ b/mission-authoring/src/__tests__/integration/missions.api.test.ts @@ -0,0 +1,365 @@ +/** + * Integration Tests for POST /api/missions (Tasks 40 & 41) + * + * Tests the complete flow from HTTP request to repository. + * Uses mocked Firestore to avoid requiring real Firebase credentials. + */ + +import { POST } from '@/app/api/missions/route'; +import { NextRequest } from 'next/server'; + +jest.mock('@/infrastructure/persistence/firebase-admin', () => ({ + getFirestoreInstance: jest.fn(() => mockFirestore), +})); + +type MissionRecord = Record; + +const mockMissions = new Map(); + +const mockFirestore = { + collection: jest.fn(() => ({ + doc: jest.fn((id?: string) => ({ + set: jest.fn(async (data: MissionRecord) => { + const docId = id || `mock-${Date.now()}`; + mockMissions.set(docId, data); + return Promise.resolve(); + }), + get: jest.fn(async () => { + const data = id ? mockMissions.get(id) : undefined; + return Promise.resolve({ + exists: !!data, + data: () => data, + id, + }); + }), + })), + where: jest.fn(() => ({ + where: jest.fn(() => ({ + where: jest.fn(() => ({ + count: jest.fn(() => ({ + get: jest.fn(async () => + Promise.resolve({ data: () => ({ count: 0 }) }) + ), + })), + })), + count: jest.fn(() => ({ + get: jest.fn(async () => + Promise.resolve({ data: () => ({ count: 0 }) }) + ), + })), + })), + count: jest.fn(() => ({ + get: jest.fn(async () => + Promise.resolve({ data: () => ({ count: 0 }) }) + ), + })), + })), + })), +}; + +let idCounter = 0; +jest.mock('nanoid', () => ({ + nanoid: jest.fn(() => `test-id-${++idCounter}`), +})); + +describe('POST /api/missions Integration Tests', () => { + beforeEach(() => { + mockMissions.clear(); + jest.clearAllMocks(); + }); + + describe('Task 40: Valid submission enters queue', () => { + it('should accept valid mission and return 201 with mission data', async () => { + const validPayload = { + yardId: 'uct-rover-1', + sessionId: 'test-session-123', + learnerId: 'test-learner', + name: 'Test Mission', + code: 'rover.forward(100)\nrover.wait(2)', + challengeId: 'M1-FORWARD', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(validPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(201); + expect(data.success).toBe(true); + expect(data.mission).toBeDefined(); + expect(data.mission.id).toBeDefined(); + expect(data.mission.yardId).toBe('uct-rover-1'); + expect(data.mission.sessionId).toBe('test-session-123'); + expect(data.mission.code).toBe('rover.forward(100)\nrover.wait(2)'); + expect(data.mission.status).toBe('queued'); + expect(data.mission.queuePosition).toBeDefined(); + expect(data.mission.estimatedWait).toBeDefined(); + }); + + it('should accept mission without optional challengeId', async () => { + const validPayload = { + yardId: 'rover-yard-2', + sessionId: 'session-456', + learnerId: 'test-learner', + name: 'Test Mission', + code: 'rover.turn_left(50)', // Updated to use approved command + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(validPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(201); + expect(data.success).toBe(true); + expect(data.mission.challengeId).toBeUndefined(); + }); + + it('should store mission in Firestore', async () => { + const validPayload = { + yardId: 'yard-1', + sessionId: 'session-789', + learnerId: 'test-learner', + name: 'Test Mission', + code: 'rover.backward(75)', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(validPayload), + }); + + await POST(request); + + expect(mockFirestore.collection).toHaveBeenCalledWith('missions'); + }); + + it('should generate unique mission ID', async () => { + const validPayload = { + yardId: 'yard-1', + sessionId: 'session-123', + learnerId: 'test-learner', + name: 'Test Mission', + code: 'rover.forward(100)', + }; + + const request1 = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(validPayload), + }); + + const request2 = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(validPayload), + }); + + const response1 = await POST(request1); + const data1 = await response1.json(); + + const response2 = await POST(request2); + const data2 = await response2.json(); + + expect(data1.mission.id).not.toBe(data2.mission.id); + }); + + it('should set submittedAt timestamp', async () => { + const validPayload = { + yardId: 'yard-1', + sessionId: 'session-123', + learnerId: 'test-learner', + name: 'Test Mission', + code: 'rover.forward(100)', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(validPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(data.mission.submittedAt).toBeDefined(); + expect(new Date(data.mission.submittedAt)).toBeInstanceOf(Date); + }); + }); + + describe('Task 41: Invalid code rejected with error message', () => { + it('should reject empty code with 400 and error message', async () => { + const invalidPayload = { + yardId: 'yard-1', + sessionId: 'session-123', + code: '', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(invalidPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.success).toBe(false); + expect(data.error).toBe('Validation failed'); + expect(data.details).toBeDefined(); + expect(data.details).toContain('code: Code cannot be empty'); + }); + + it('should reject whitespace-only code', async () => { + const invalidPayload = { + yardId: 'yard-1', + sessionId: 'session-123', + code: ' \n\t ', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(invalidPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.success).toBe(false); + expect(data.details).toContain('code: Code cannot be only whitespace'); + }); + + it('should reject missing required fields', async () => { + const invalidPayload = { + yardId: 'yard-1', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(invalidPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.success).toBe(false); + expect(data.error).toBe('Validation failed'); + expect(data.details.length).toBeGreaterThan(0); + }); + + it('should reject empty yardId', async () => { + const invalidPayload = { + yardId: '', + sessionId: 'session-123', + code: 'rover.forward(100)', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(invalidPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.success).toBe(false); + expect(data.details).toContain('yardId: Yard ID is required'); + }); + + it('should reject invalid yardId format', async () => { + const invalidPayload = { + yardId: 'yard@invalid!', + sessionId: 'session-123', + code: 'rover.forward(100)', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(invalidPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.success).toBe(false); + }); + + it('should reject code exceeding maximum length', async () => { + const invalidPayload = { + yardId: 'yard-1', + sessionId: 'session-123', + code: 'a'.repeat(10001), + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(invalidPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.success).toBe(false); + }); + + it('should return multiple validation errors for multiple invalid fields', async () => { + const invalidPayload = { + yardId: '', + sessionId: '', + code: '', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(invalidPayload), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.success).toBe(false); + expect(data.details.length).toBeGreaterThanOrEqual(3); + }); + + it('should not store invalid mission in Firestore', async () => { + const invalidPayload = { + yardId: 'yard-1', + sessionId: 'session-123', + code: '', + }; + + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: JSON.stringify(invalidPayload), + }); + + await POST(request); + + expect(mockMissions.size).toBe(0); + }); + }); + + describe('Error handling', () => { + it('should return 500 for malformed JSON', async () => { + const request = new NextRequest('http://localhost:3000/api/missions', { + method: 'POST', + body: 'invalid-json', + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(500); + expect(data.success).toBe(false); + }); + }); +}); diff --git a/mission-authoring/src/__tests__/integration/queue-order.test.ts b/mission-authoring/src/__tests__/integration/queue-order.test.ts new file mode 100644 index 0000000..965c1d1 --- /dev/null +++ b/mission-authoring/src/__tests__/integration/queue-order.test.ts @@ -0,0 +1,3 @@ +describe('queue order integration scaffold', () => { + it.todo('Task 47: submitted missions appear in the correct queue order'); +}); diff --git a/mission-authoring/src/__tests__/integration/simulator-execution.test.ts b/mission-authoring/src/__tests__/integration/simulator-execution.test.ts new file mode 100644 index 0000000..d412488 --- /dev/null +++ b/mission-authoring/src/__tests__/integration/simulator-execution.test.ts @@ -0,0 +1,3 @@ +describe('simulator execution scaffold', () => { + it.todo('Task 32: submitted code updates simulator state through the execution engine'); +}); diff --git a/mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts b/mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts new file mode 100644 index 0000000..2154d5d --- /dev/null +++ b/mission-authoring/src/__tests__/integration/unsafe-module-blocked.test.ts @@ -0,0 +1,361 @@ +/** + * Integration Tests for Task 25: Unsafe Module Blocking + * + * User Story 21: As a learner I want the editor to restrict available commands + * to approved rover functions so that I cannot submit unsafe code. + * + * Test Coverage: + * - End-to-end validation via POST /api/missions + * - Unsafe imports are rejected at API level + * - Clear error messages returned to client + * - Safe code is accepted + * + * Testing Strategy: + * - Integration tests using real validation pipeline + * - Tests full flow from HTTP request to response + * - Verifies both success and failure paths + */ + +import { validateMission } from '@/infrastructure/validation/schemas'; + +describe('unsafe import blocking (integration)', () => { + /** + * Test Case 1: Safe rover code passes validation + * Baseline test - verify normal missions work + */ + it('accepts mission with safe rover commands', () => { + const safeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-001', + code: ` +rover.forward(100) +rover.turn_left(90) +rover.forward(50) + `.trim(), + challengeId: 'M1-FORWARD', + }; + + const result = validateMission(safeMissionData); + + expect(result.success).toBe(true); + expect(result.data).toBeDefined(); + expect(result.errors).toBeUndefined(); + }); + + /** + * Test Case 2: Block 'os' module import + * Most common dangerous import attempt + */ + it('rejects mission with os module import', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-002', + code: ` +import os +rover.forward(100) +os.system('rm -rf /') + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors).toBeDefined(); + expect(result.errors![0]).toContain('os'); + expect(result.errors![0]).toContain('not allowed'); + }); + + /** + * Test Case 3: Block 'subprocess' module + * Prevents command execution + */ + it('rejects mission with subprocess import', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-003', + code: ` +import subprocess +subprocess.run(['echo', 'hacked']) + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors).toBeDefined(); + expect(result.errors![0]).toContain('subprocess'); + }); + + /** + * Test Case 4: Block 'socket' module + * Prevents network access + */ + it('rejects mission with socket import', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-004', + code: ` +import socket +s = socket.socket() + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors![0]).toContain('socket'); + }); + + /** + * Test Case 5: Block 'from ... import' pattern + * Test alternative import syntax + */ + it('rejects mission with from...import pattern', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-005', + code: ` +from os import path +print(path.exists('/')) + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors![0]).toContain('os'); + }); + + /** + * Test Case 6: Block non-rover function calls + * Prevent typos and unknown commands + */ + it('rejects mission with non-approved rover commands', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-006', + code: ` +rover.hack_mainframe() +rover.delete_all_files() + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors).toBeDefined(); + expect(result.errors![0]).toContain('hack_mainframe'); + }); + + /** + * Test Case 7: Block eval() built-in + * Prevent code injection + */ + it('rejects mission with eval() built-in', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-007', + code: ` +code = "rover.forward(100)" +eval(code) + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors![0]).toContain('eval'); + }); + + /** + * Test Case 8: Block exec() built-in + * Prevent code execution + */ + it('rejects mission with exec() built-in', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-008', + code: ` +exec("import os; os.system('ls')") + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors![0]).toContain('exec'); + }); + + /** + * Test Case 9: Block open() for file I/O + * Prevent file system access + */ + it('rejects mission with open() built-in', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-009', + code: ` +with open('/etc/passwd', 'r') as f: + print(f.read()) + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors![0]).toContain('open'); + }); + + /** + * Test Case 10: Multiple violations reported + * Verify all issues are caught + */ + it('reports multiple violations in single mission', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-010', + code: ` +import os +import sys +rover.hack() +eval("print('test')") + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors).toBeDefined(); + expect(result.errors!.length).toBeGreaterThan(1); + }); + + /** + * Test Case 11: Safe Python with loops and conditionals + * Verify legitimate Python constructs work + */ + it('accepts mission with loops and conditionals', () => { + const safeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-011', + code: ` +for i in range(4): + if i % 2 == 0: + rover.forward(100) + else: + rover.turn_left(90) + rover.wait(1) + `.trim(), + }; + + const result = validateMission(safeMissionData); + + expect(result.success).toBe(true); + expect(result.data).toBeDefined(); + }); + + /** + * Test Case 12: Safe Python with print statements + * Verify debugging helpers work + */ + it('accepts mission with print statements for debugging', () => { + const safeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-012', + code: ` +print("Starting mission") +rover.forward(100) +print("Mission complete") + `.trim(), + }; + + const result = validateMission(safeMissionData); + + expect(result.success).toBe(true); + }); + + /** + * Test Case 13: Error includes line numbers + * Help learners locate issues + */ + it('includes line numbers in error messages', () => { + const unsafeMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-013', + code: ` +rover.forward(100) +import os +rover.backward(50) + `.trim(), + }; + + const result = validateMission(unsafeMissionData); + + expect(result.success).toBe(false); + expect(result.errors![0]).toContain('line'); + expect(result.errors![0]).toContain('2'); + }); + + /** + * Test Case 14: Schema validation still enforced + * Verify allowlist doesn't bypass schema checks + */ + it('still enforces schema validation (missing required fields)', () => { + const invalidMissionData = { + // Missing yardId and sessionId + code: 'rover.forward(100)', + }; + + const result = validateMission(invalidMissionData); + + expect(result.success).toBe(false); + expect(result.errors).toBeDefined(); + // Should fail on schema, not allowlist + // Zod uses "Required" in error messages + expect(result.errors![0]).toContain('Required'); + }); + + /** + * Test Case 15: Empty code rejected + * Verify basic validation still works + */ + it('rejects empty code', () => { + const invalidMissionData = { + yardId: 'uct-rover-1', + learnerId: 'test-learner', + name: 'Test Mission', + sessionId: 'test-session-015', + code: '', + }; + + const result = validateMission(invalidMissionData); + + expect(result.success).toBe(false); + expect(result.errors![0]).toContain('empty'); + }); +}); diff --git a/mission-authoring/src/__tests__/integration/video-display.test.ts b/mission-authoring/src/__tests__/integration/video-display.test.ts new file mode 100644 index 0000000..2f895ae --- /dev/null +++ b/mission-authoring/src/__tests__/integration/video-display.test.ts @@ -0,0 +1,234 @@ +/** + * Integration Tests for Task 108: Video Display in Completed Missions + * + * User Story 102: As a learner I want to view and download my mission video + * on the platform so that I can see exactly what my code caused the rover to do. + * + * Test Coverage: + * - Video URL presence in completed missions + * - Video field validation + * - Mission entity includes video fields + * + * Testing Strategy: + * - Test mission entity structure + * - Validate video URL formats + * - Verify video fields in completed missions + */ + +import type { Mission } from '@/core/domain/entities/Mission'; + +describe('Video Display Integration', () => { + describe('Mission Entity Video Fields', () => { + /** + * Test Case 1: Mission entity includes videoUrl field + */ + it('mission entity supports videoUrl field', () => { + const mission: Mission = { + id: 'test-mission-1', + yardId: 'uct-rover-1', + sessionId: 'session-123', + code: 'rover.forward(100)', + status: 'completed', + submittedAt: new Date().toISOString(), + videoUrl: 'https://storage.googleapis.com/test-bucket/mission-1.mp4', + }; + + expect(mission.videoUrl).toBeDefined(); + expect(typeof mission.videoUrl).toBe('string'); + }); + + /** + * Test Case 2: Mission entity supports youtubeUrl field + */ + it('mission entity supports youtubeUrl field', () => { + const mission: Mission = { + id: 'test-mission-2', + yardId: 'uct-rover-1', + sessionId: 'session-123', + code: 'rover.forward(100)', + status: 'completed', + submittedAt: new Date().toISOString(), + youtubeUrl: 'https://www.youtube.com/watch?v=test123', + }; + + expect(mission.youtubeUrl).toBeDefined(); + expect(typeof mission.youtubeUrl).toBe('string'); + }); + + /** + * Test Case 3: Mission can have both videoUrl and youtubeUrl + */ + it('mission entity supports both video fields simultaneously', () => { + const mission: Mission = { + id: 'test-mission-3', + yardId: 'uct-rover-1', + sessionId: 'session-123', + code: 'rover.forward(100)', + status: 'completed', + submittedAt: new Date().toISOString(), + videoUrl: 'https://storage.googleapis.com/test-bucket/mission-3.mp4', + youtubeUrl: 'https://www.youtube.com/watch?v=test456', + }; + + expect(mission.videoUrl).toBeDefined(); + expect(mission.youtubeUrl).toBeDefined(); + }); + + /** + * Test Case 4: Video fields are optional + */ + it('video fields are optional in mission entity', () => { + const mission: Mission = { + id: 'test-mission-4', + yardId: 'uct-rover-1', + sessionId: 'session-123', + code: 'rover.forward(100)', + status: 'queued', + submittedAt: new Date().toISOString(), + }; + + expect(mission.videoUrl).toBeUndefined(); + expect(mission.youtubeUrl).toBeUndefined(); + }); + }); + + describe('Video URL Validation', () => { + /** + * Test Case 5: Google Cloud Storage URL format is valid + */ + it('accepts valid GCS URL format', () => { + const gcsUrl = 'https://storage.googleapis.com/rover-videos/mission-123.mp4'; + + expect(gcsUrl).toMatch(/^https:\/\/storage\.googleapis\.com\//); + expect(gcsUrl).toMatch(/\.mp4$/); + }); + + /** + * Test Case 6: YouTube URL format is valid + */ + it('accepts valid YouTube URL format', () => { + const youtubeUrl = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; + + expect(youtubeUrl).toMatch(/^https:\/\/www\.youtube\.com\/watch\?v=/); + }); + + /** + * Test Case 7: Short YouTube URL format is valid + */ + it('accepts valid youtu.be URL format', () => { + const shortYoutubeUrl = 'https://youtu.be/dQw4w9WgXcQ'; + + expect(shortYoutubeUrl).toMatch(/^https:\/\/youtu\.be\//); + }); + }); + + describe('Completed Mission Video Workflow', () => { + /** + * Test Case 8: Completed mission can have video + */ + it('completed mission includes video URL', () => { + const completedMission: Mission = { + id: 'completed-1', + yardId: 'uct-rover-1', + sessionId: 'session-456', + code: 'rover.forward(100)\nrover.turn_left(90)', + status: 'completed', + submittedAt: '2026-04-20T10:00:00Z', + startedAt: '2026-04-20T10:05:00Z', + completedAt: '2026-04-20T10:06:30Z', + videoUrl: 'https://storage.googleapis.com/rover-videos/completed-1.mp4', + executionResult: { + isSuccessful: true, + consoleOutput: 'Mission completed successfully', + }, + }; + + expect(completedMission.status).toBe('completed'); + expect(completedMission.videoUrl).toBeDefined(); + expect(completedMission.completedAt).toBeDefined(); + }); + + /** + * Test Case 9: Failed mission can also have video + */ + it('failed mission includes video URL showing failure', () => { + const failedMission: Mission = { + id: 'failed-1', + yardId: 'uct-rover-1', + sessionId: 'session-789', + code: 'rover.forward(9999)', + status: 'failed', + submittedAt: '2026-04-20T11:00:00Z', + startedAt: '2026-04-20T11:05:00Z', + completedAt: '2026-04-20T11:05:15Z', + videoUrl: 'https://storage.googleapis.com/rover-videos/failed-1.mp4', + executionResult: { + isSuccessful: false, + consoleOutput: 'Execution error', + errorMessage: 'Distance out of bounds', + }, + }; + + expect(failedMission.status).toBe('failed'); + expect(failedMission.videoUrl).toBeDefined(); + expect(failedMission.executionResult?.isSuccessful).toBe(false); + }); + + /** + * Test Case 10: Queued mission does not have video yet + */ + it('queued mission does not have video URL', () => { + const queuedMission: Mission = { + id: 'queued-1', + yardId: 'uct-rover-1', + sessionId: 'session-101', + code: 'rover.forward(50)', + status: 'queued', + submittedAt: '2026-04-20T12:00:00Z', + queuePosition: 3, + estimatedWait: 180, + }; + + expect(queuedMission.status).toBe('queued'); + expect(queuedMission.videoUrl).toBeUndefined(); + expect(queuedMission.completedAt).toBeUndefined(); + }); + + /** + * Test Case 11: Processing mission does not have video yet + */ + it('processing mission does not have video URL', () => { + const processingMission: Mission = { + id: 'processing-1', + yardId: 'uct-rover-1', + sessionId: 'session-202', + code: 'rover.turn_right(180)', + status: 'processing', + submittedAt: '2026-04-20T13:00:00Z', + startedAt: '2026-04-20T13:05:00Z', + }; + + expect(processingMission.status).toBe('processing'); + expect(processingMission.videoUrl).toBeUndefined(); + expect(processingMission.completedAt).toBeUndefined(); + }); + }); + + describe('Video File Types', () => { + /** + * Test Case 12: MP4 video format is supported + */ + it('supports MP4 video format', () => { + const mp4Url = 'https://storage.googleapis.com/videos/mission.mp4'; + expect(mp4Url).toMatch(/\.mp4$/); + }); + + /** + * Test Case 13: WebM video format is supported + */ + it('supports WebM video format', () => { + const webmUrl = 'https://storage.googleapis.com/videos/mission.webm'; + expect(webmUrl).toMatch(/\.webm$/); + }); + }); +}); diff --git a/mission-authoring/src/__tests__/integration/websocket-delivery.test.ts b/mission-authoring/src/__tests__/integration/websocket-delivery.test.ts new file mode 100644 index 0000000..97599b1 --- /dev/null +++ b/mission-authoring/src/__tests__/integration/websocket-delivery.test.ts @@ -0,0 +1,3 @@ +describe('websocket delivery scaffold', () => { + it.todo('Task 69: mission payload is delivered over the WebSocket connection'); +}); diff --git a/mission-authoring/src/__tests__/unit/FirestoreMissionRepository.test.ts b/mission-authoring/src/__tests__/unit/FirestoreMissionRepository.test.ts new file mode 100644 index 0000000..8975086 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/FirestoreMissionRepository.test.ts @@ -0,0 +1,122 @@ +import { FirestoreMissionRepository } from '@/infrastructure/persistence/FirestoreMissionRepository'; +import type { Firestore } from 'firebase/firestore'; + +jest.mock('nanoid', () => ({ + nanoid: jest.fn(() => 'testmissionid12345'), +})); + +describe('FirestoreMissionRepository', () => { + it('omits undefined fields before writing to Firestore', async () => { + const set = jest.fn(async () => Promise.resolve()); + const countGet = jest.fn(async () => Promise.resolve({ data: () => ({ count: 0 }) })); + + const firestore = { + collection: jest.fn(() => ({ + doc: jest.fn(() => ({ + set, + })), + where: jest.fn(() => ({ + where: jest.fn(() => ({ + where: jest.fn(() => ({ + count: jest.fn(() => ({ + get: countGet, + })), + })), + })), + })), + })), + } as unknown as Firestore; + + const repository = new FirestoreMissionRepository(firestore); + + await repository.create({ + yardId: 'yard-1', + sessionId: 'session-1', + learnerUid: undefined, + code: 'rover.forward(100)', + challengeId: undefined, + status: 'queued', + executionResult: { + isSuccessful: false, + consoleOutput: 'output', + errorMessage: undefined, + }, + videoUrl: undefined, + youtubeUrl: undefined, + submittedAt: new Date().toISOString(), + startedAt: undefined, + completedAt: undefined, + }); + + expect(set).toHaveBeenCalledTimes(1); + + const writtenMission = set.mock.calls[0][0]; + + expect(writtenMission).toMatchObject({ + yardId: 'yard-1', + sessionId: 'session-1', + code: 'rover.forward(100)', + status: 'queued', + executionResult: { + isSuccessful: false, + consoleOutput: 'output', + }, + }); + + expect(writtenMission).not.toHaveProperty('challengeId'); + expect(writtenMission).not.toHaveProperty('learnerUid'); + expect(writtenMission).not.toHaveProperty('videoUrl'); + expect(writtenMission).not.toHaveProperty('youtubeUrl'); + expect(writtenMission).not.toHaveProperty('startedAt'); + expect(writtenMission).not.toHaveProperty('completedAt'); + expect(writtenMission.executionResult).not.toHaveProperty('errorMessage'); + }); + + it('strips comments from mission code before writing to Firestore', async () => { + const set = jest.fn(async () => Promise.resolve()); + const countGet = jest.fn(async () => Promise.resolve({ data: () => ({ count: 0 }) })); + + const firestore = { + collection: jest.fn(() => ({ + doc: jest.fn(() => ({ + set, + })), + where: jest.fn(() => ({ + where: jest.fn(() => ({ + where: jest.fn(() => ({ + count: jest.fn(() => ({ + get: countGet, + })), + })), + })), + })), + })), + } as unknown as Firestore; + + const repository = new FirestoreMissionRepository(firestore); + + await repository.create({ + yardId: 'yard-1', + sessionId: 'session-1', + learnerUid: undefined, + code: '# comment line\nrover.forward(100) # move\n\n# another comment\nrover.stop()', + challengeId: undefined, + status: 'queued', + executionResult: { + isSuccessful: false, + consoleOutput: 'output', + errorMessage: undefined, + }, + videoUrl: undefined, + youtubeUrl: undefined, + submittedAt: new Date().toISOString(), + startedAt: undefined, + completedAt: undefined, + }); + + const writtenMission = set.mock.calls[0][0]; + + expect(writtenMission.code).toBe('rover.forward(100)\nrover.stop()'); + expect(writtenMission.id).toBe('testmissionid12345'); + }); +}); diff --git a/mission-authoring/src/__tests__/unit/Learner.test.ts b/mission-authoring/src/__tests__/unit/Learner.test.ts new file mode 100644 index 0000000..2f58177 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/Learner.test.ts @@ -0,0 +1,202 @@ +/** + * Unit Tests: Learner Entity + */ + +import { + Learner, + createAnonymousLearner, + isActiveLearner, + sanitizeDisplayName, +} from '@/core/domain/entities/Learner'; + +describe('Learner Entity', () => { + describe('createAnonymousLearner', () => { + it('creates a learner with valid defaults', () => { + const sessionId = 'test-session-123'; + const learner = createAnonymousLearner(sessionId); + + expect(learner.id).toBe(sessionId); + expect(learner.sessionId).toBe(sessionId); + expect(learner.missionCount).toBe(0); + expect(learner.completedMissions).toBe(0); + expect(learner.devices).toHaveLength(1); + expect(learner.devices[0].sessionId).toBe(sessionId); + }); + + it('assigns a random avatar color', () => { + const learner = createAnonymousLearner('test-123'); + + expect(learner.avatarColor).toBeDefined(); + expect(learner.avatarColor).toMatch(/^#[0-9A-F]{6}$/i); + }); + + it('sets timestamps correctly', () => { + const before = new Date().getTime(); + const learner = createAnonymousLearner('test-123'); + const after = new Date().getTime(); + + const createdTime = new Date(learner.createdAt).getTime(); + + expect(createdTime).toBeGreaterThanOrEqual(before); + expect(createdTime).toBeLessThanOrEqual(after); + expect(learner.lastActiveAt).toBe(learner.createdAt); + }); + + it('initializes device array with first device', () => { + const sessionId = 'test-123'; + const learner = createAnonymousLearner(sessionId); + + expect(learner.devices).toHaveLength(1); + expect(learner.devices[0]).toEqual({ + sessionId, + firstSeenAt: learner.createdAt, + lastSeenAt: learner.createdAt, + }); + }); + }); + + describe('isActiveLearner', () => { + it('returns false for new learner with no missions', () => { + const learner = createAnonymousLearner('test-123'); + + expect(isActiveLearner(learner)).toBe(false); + }); + + it('returns true for learner with missions', () => { + const learner = createAnonymousLearner('test-123'); + learner.missionCount = 5; + + expect(isActiveLearner(learner)).toBe(true); + }); + + it('returns true even with zero completions', () => { + const learner = createAnonymousLearner('test-123'); + learner.missionCount = 1; + learner.completedMissions = 0; + + expect(isActiveLearner(learner)).toBe(true); + }); + }); + + describe('sanitizeDisplayName', () => { + it('removes email addresses', () => { + const input = 'contact me at test@example.com'; + const sanitized = sanitizeDisplayName(input); + + expect(sanitized).not.toContain('test@example.com'); + expect(sanitized).toBe('contact me at'); + }); + + it('trims whitespace', () => { + const input = ' RoverPilot '; + const sanitized = sanitizeDisplayName(input); + + expect(sanitized).toBe('RoverPilot'); + }); + + it('limits length to 20 characters', () => { + const input = 'ThisIsAVeryLongNicknameThatExceedsTheLimit'; + const sanitized = sanitizeDisplayName(input); + + expect(sanitized).toHaveLength(20); + expect(sanitized).toBe('ThisIsAVeryLongNickn'); + }); + + it('handles multiple emails', () => { + const input = 'email test@example.com or admin@site.org'; + const sanitized = sanitizeDisplayName(input); + + expect(sanitized).not.toContain('@'); + }); + + it('preserves valid nicknames', () => { + const validNames = [ + 'RoverPilot123', + 'Mars_Explorer', + 'Commander_X', + 'Pilot-42', + ]; + + validNames.forEach((name) => { + const sanitized = sanitizeDisplayName(name); + expect(sanitized).toBe(name); + }); + }); + + it('handles empty string', () => { + const sanitized = sanitizeDisplayName(''); + + expect(sanitized).toBe(''); + }); + + it('handles only whitespace', () => { + const sanitized = sanitizeDisplayName(' '); + + expect(sanitized).toBe(''); + }); + }); + + describe('Learner statistics', () => { + it('calculates success rate correctly', () => { + const learner = createAnonymousLearner('test-123'); + learner.missionCount = 10; + learner.completedMissions = 8; + + const successRate = (learner.completedMissions / learner.missionCount) * 100; + + expect(successRate).toBe(80); + }); + + it('handles zero missions for success rate', () => { + const learner = createAnonymousLearner('test-123'); + + const successRate = + learner.missionCount > 0 + ? (learner.completedMissions / learner.missionCount) * 100 + : 0; + + expect(successRate).toBe(0); + }); + + it('handles perfect success rate', () => { + const learner = createAnonymousLearner('test-123'); + learner.missionCount = 5; + learner.completedMissions = 5; + + const successRate = (learner.completedMissions / learner.missionCount) * 100; + + expect(successRate).toBe(100); + }); + }); + + describe('Multi-device support', () => { + it('can track multiple devices', () => { + const learner = createAnonymousLearner('test-123'); + + learner.devices.push({ + sessionId: 'device-2', + firstSeenAt: new Date().toISOString(), + lastSeenAt: new Date().toISOString(), + }); + + expect(learner.devices).toHaveLength(2); + }); + + it('maintains device history', () => { + const learner = createAnonymousLearner('test-123'); + const device1Time = learner.devices[0].firstSeenAt; + + // Simulate second device + const laterTime = new Date(Date.now() + 1000).toISOString(); + learner.devices.push({ + sessionId: 'device-2', + firstSeenAt: laterTime, + lastSeenAt: laterTime, + }); + + expect(new Date(learner.devices[1].firstSeenAt).getTime()).toBeGreaterThan( + new Date(device1Time).getTime() + ); + }); + }); +}); diff --git a/mission-authoring/src/__tests__/unit/MissionService.test.ts b/mission-authoring/src/__tests__/unit/MissionService.test.ts new file mode 100644 index 0000000..07664f5 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/MissionService.test.ts @@ -0,0 +1,252 @@ +/** + * Unit Tests for MissionService + * + * Tests business logic layer in isolation using mocked repository. + * Demonstrates Repository pattern benefits for testability. + */ + +import { MissionService } from '@/core/application/services/MissionService'; +import { IMissionRepository } from '@/core/domain/repositories/IMissionRepository'; +import { Mission } from '@/core/domain/entities/Mission'; +import { CreateMissionDto } from '@/infrastructure/validation/schemas'; + +class MockMissionRepository implements IMissionRepository { + private missions: Map = new Map(); + private idCounter = 0; + + async create(mission: Omit): Promise { + const id = `mock-id-${++this.idCounter}`; + const newMission: Mission = { + ...mission, + id, + queuePosition: 1, + estimatedWait: 0, + }; + this.missions.set(id, newMission); + return newMission; + } + + async findById(id: string): Promise { + return this.missions.get(id) || null; + } + + async findByLearnerId(learnerId: string): Promise { + return Array.from(this.missions.values()) + .filter((m) => m.learnerId === learnerId) + .sort((a, b) => b.submittedAt.localeCompare(a.submittedAt)); + } + + async findBySessionId(sessionId: string): Promise { + return Array.from(this.missions.values()) + .filter((m) => m.sessionId === sessionId) + .sort((a, b) => b.submittedAt.localeCompare(a.submittedAt)); + } + + async getQueuedMissions(yardId: string): Promise { + return Array.from(this.missions.values()) + .filter((m) => m.yardId === yardId && m.status === 'queued') + .sort((a, b) => a.submittedAt.localeCompare(b.submittedAt)); + } + + async update(id: string, updates: Partial): Promise { + const mission = this.missions.get(id); + if (!mission) return null; + + const updated = { ...mission, ...updates }; + this.missions.set(id, updated); + return updated; + } + + async getQueueLength(yardId: string): Promise { + return Array.from(this.missions.values()).filter( + (m) => m.yardId === yardId && m.status === 'queued' + ).length; + } + + async findAll(): Promise { + return Array.from(this.missions.values()).sort((a, b) => + b.submittedAt.localeCompare(a.submittedAt) + ); + } + + clear() { + this.missions.clear(); + } +} + +/** + * Build a fully-typed CreateMissionDto, filling required fields with defaults + * so individual tests only specify what they care about. + */ +const makeDto = ( + overrides: Partial & { yardId: string; code: string } +): CreateMissionDto => ({ + learnerId: 'learner-123', + sessionId: 'session-123', + name: 'Test Mission', + ...overrides, +}); + +describe('MissionService', () => { + let service: MissionService; + let repository: MockMissionRepository; + + beforeEach(() => { + repository = new MockMissionRepository(); + service = new MissionService(repository); + }); + + describe('submitMission', () => { + it('should successfully submit a mission', async () => { + const result = await service.submitMission( + makeDto({ yardId: 'yard-1', code: 'rover.forward(100)', challengeId: 'M1' }) + ); + + expect(result.success).toBe(true); + expect(result.mission).toBeDefined(); + expect(result.mission?.yardId).toBe('yard-1'); + expect(result.mission?.learnerId).toBe('learner-123'); + expect(result.mission?.code).toBe('rover.forward(100)'); + expect(result.mission?.status).toBe('queued'); + expect(result.mission?.id).toBeDefined(); + }); + + it('should submit mission without optional challengeId', async () => { + const result = await service.submitMission( + makeDto({ yardId: 'yard-1', code: 'rover.spinLeft(50)' }) + ); + + expect(result.success).toBe(true); + expect(result.mission?.challengeId).toBeUndefined(); + }); + + it('should set initial status to queued', async () => { + const result = await service.submitMission( + makeDto({ yardId: 'yard-1', code: 'rover.forward(100)' }) + ); + + expect(result.mission?.status).toBe('queued'); + }); + + it('should include queue position and estimated wait', async () => { + const result = await service.submitMission( + makeDto({ yardId: 'yard-1', code: 'rover.forward(100)' }) + ); + + expect(result.mission?.queuePosition).toBeDefined(); + expect(result.mission?.estimatedWait).toBeDefined(); + }); + }); + + describe('getMissionById', () => { + it('should retrieve existing mission', async () => { + const submitted = await service.submitMission( + makeDto({ yardId: 'yard-1', code: 'rover.forward(100)' }) + ); + const retrieved = await service.getMissionById(submitted.mission!.id); + + expect(retrieved).toEqual(submitted.mission); + }); + + it('should return null for non-existent mission', async () => { + const retrieved = await service.getMissionById('non-existent-id'); + + expect(retrieved).toBeNull(); + }); + }); + + describe('getMissionHistory', () => { + it('should retrieve all missions for a learner', async () => { + await service.submitMission( + makeDto({ yardId: 'yard-1', learnerId: 'learner-123', code: 'rover.forward(100)' }) + ); + + await service.submitMission( + makeDto({ yardId: 'yard-2', learnerId: 'learner-123', code: 'rover.backward(50)' }) + ); + + const history = await service.getMissionHistory('learner-123'); + + expect(history).toHaveLength(2); + expect(history.every((m) => m.learnerId === 'learner-123')).toBe(true); + }); + + it('should return empty array for learner with no missions', async () => { + const history = await service.getMissionHistory('unknown-learner'); + + expect(history).toEqual([]); + }); + + it('should not return missions from other learners', async () => { + await service.submitMission( + makeDto({ yardId: 'yard-1', learnerId: 'learner-123', code: 'rover.forward(100)' }) + ); + + await service.submitMission( + makeDto({ yardId: 'yard-1', learnerId: 'learner-456', code: 'rover.backward(50)' }) + ); + + const history = await service.getMissionHistory('learner-123'); + + expect(history).toHaveLength(1); + expect(history[0].learnerId).toBe('learner-123'); + }); + }); + + describe('getQueueForYard', () => { + it('should retrieve all queued missions for a yard', async () => { + await service.submitMission( + makeDto({ yardId: 'yard-1', learnerId: 'learner-123', code: 'rover.forward(100)' }) + ); + + await service.submitMission( + makeDto({ yardId: 'yard-1', learnerId: 'learner-456', code: 'rover.backward(50)' }) + ); + + const queue = await service.getQueueForYard('yard-1'); + + expect(queue).toHaveLength(2); + expect(queue.every((m) => m.yardId === 'yard-1')).toBe(true); + expect(queue.every((m) => m.status === 'queued')).toBe(true); + }); + + it('should not return missions from other yards', async () => { + await service.submitMission( + makeDto({ yardId: 'yard-1', code: 'rover.forward(100)' }) + ); + + await service.submitMission( + makeDto({ yardId: 'yard-2', code: 'rover.backward(50)' }) + ); + + const queue = await service.getQueueForYard('yard-1'); + + expect(queue).toHaveLength(1); + expect(queue[0].yardId).toBe('yard-1'); + }); + }); + + describe('updateMission', () => { + it('should update mission status', async () => { + const submitted = await service.submitMission( + makeDto({ yardId: 'yard-1', code: 'rover.forward(100)' }) + ); + + const updated = await service.updateMission(submitted.mission!.id, { + status: 'processing', + startedAt: new Date().toISOString(), + }); + + expect(updated?.status).toBe('processing'); + expect(updated?.startedAt).toBeDefined(); + }); + + it('should return null for non-existent mission', async () => { + const updated = await service.updateMission('non-existent-id', { + status: 'completed', + }); + + expect(updated).toBeNull(); + }); + }); +}); diff --git a/mission-authoring/src/__tests__/unit/allowlist.test.ts b/mission-authoring/src/__tests__/unit/allowlist.test.ts new file mode 100644 index 0000000..dcecb69 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/allowlist.test.ts @@ -0,0 +1,364 @@ +/** + * Unit Tests for Task 24: Allowlist Enforcement + * + * User Story 21: As a learner I want the editor to restrict available commands + * to approved rover functions so that I cannot submit unsafe code. + * + * Test Coverage: + * - Approved rover commands pass validation + * - Disallowed imports are blocked + * - Non-rover function calls are blocked + * - Dangerous built-ins are blocked + * - Clear error messages are returned + * + * Testing Strategy: + * - Test AllowlistService and AST analyzer + * - Cover both safe and unsafe code patterns + * - Verify line number reporting for errors + * - Test edge cases (comments, multi-line, etc.) + */ + +import { AllowlistService } from '@/core/application/services/AllowlistService'; +import { analyzeCodeForAllowlist } from '@/infrastructure/sandbox/ast-allowlist-analyzer'; + +describe('allowlist enforcement', () => { + let service: AllowlistService; + + beforeEach(() => { + service = new AllowlistService(); + }); + + describe('approved rover commands', () => { + /** + * Test Case 1: Valid rover movement commands pass + * Verifies that approved rover commands are allowed + */ + it('allows approved rover movement commands', () => { + const safeCode = ` +rover.forward(100) +rover.backward(50) +rover.turn_left(90) +rover.turn_right(45) + `.trim(); + + const result = service.analyze(safeCode); + + expect(result.isValid).toBe(true); + expect(result.findings).toHaveLength(0); + }); + + /** + * Test Case 2: Valid rover utility commands pass + * Verifies wait, stop, and sensor commands + */ + it('allows approved rover utility commands', () => { + const safeCode = ` +rover.wait(2) +rover.stop() +distance = rover.get_distance() +heading = rover.get_heading() + `.trim(); + + const result = service.analyze(safeCode); + + expect(result.isValid).toBe(true); + expect(result.findings).toHaveLength(0); + }); + + /** + * Test Case 3: Python built-ins are allowed + * Verifies print, range, and control flow + */ + it('allows safe Python built-ins', () => { + const safeCode = ` +for i in range(3): + print("Moving forward") + rover.forward(100) + rover.wait(1) + `.trim(); + + const result = service.analyze(safeCode); + + expect(result.isValid).toBe(true); + expect(result.findings).toHaveLength(0); + }); + }); + + describe('disallowed imports', () => { + /** + * Test Case 4: Block 'os' module import + * Most common dangerous import + */ + it('blocks os module import', () => { + const unsafeCode = ` +import os +rover.forward(100) + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings).toHaveLength(1); + expect(result.findings[0].ruleId).toBe('disallowed-import'); + expect(result.findings[0].message).toContain('os'); + expect(result.findings[0].line).toBe(1); + }); + + /** + * Test Case 5: Block 'subprocess' module + * Prevents command execution + */ + it('blocks subprocess module import', () => { + const unsafeCode = ` +import subprocess +subprocess.run(['rm', '-rf', '/']) + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings).toHaveLength(1); + expect(result.findings[0].ruleId).toBe('disallowed-import'); + expect(result.findings[0].message).toContain('subprocess'); + }); + + /** + * Test Case 6: Block 'from ... import' pattern + * Test alternative import syntax + */ + it('blocks from ... import pattern', () => { + const unsafeCode = ` +from os import path +print(path.exists('/etc/passwd')) + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings).toHaveLength(1); + expect(result.findings[0].message).toContain('os'); + }); + + /** + * Test Case 7: Block socket module + * Prevents network access + */ + it('blocks socket module for network access', () => { + const unsafeCode = ` +import socket +s = socket.socket() + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings[0].message).toContain('socket'); + }); + + /** + * Test Case 8: Block sys module + * Prevents system introspection + */ + it('blocks sys module', () => { + const unsafeCode = ` +import sys +sys.exit(1) + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings[0].message).toContain('sys'); + }); + }); + + describe('non-rover function calls', () => { + /** + * Test Case 9: Block unapproved rover commands + * Catch typos and unknown commands + */ + it('blocks non-existent rover commands', () => { + const unsafeCode = ` +rover.hack_mainframe() +rover.delete_all_data() + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings.length).toBeGreaterThan(0); + expect(result.findings[0].ruleId).toBe('disallowed-function'); + expect(result.findings[0].message).toContain('hack_mainframe'); + }); + + /** + * Test Case 10: Detect multiple violations + * Test that all violations are reported + */ + it('reports all violations in code', () => { + const unsafeCode = ` +import os +rover.hack() +import sys + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings.length).toBeGreaterThanOrEqual(2); + }); + }); + + describe('dangerous built-ins', () => { + /** + * Test Case 11: Block eval() function + * Prevents code injection + */ + it('blocks eval() built-in', () => { + const unsafeCode = ` +code = "rover.forward(100)" +eval(code) + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings[0].ruleId).toBe('dangerous-builtin'); + expect(result.findings[0].message).toContain('eval'); + }); + + /** + * Test Case 12: Block exec() function + * Prevents code execution + */ + it('blocks exec() built-in', () => { + const unsafeCode = ` +exec("import os") + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings[0].message).toContain('exec'); + }); + + /** + * Test Case 13: Block open() for file I/O + * Prevents file system access + */ + it('blocks open() built-in for file access', () => { + const unsafeCode = ` +f = open('/etc/passwd', 'r') + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings[0].message).toContain('open'); + }); + + /** + * Test Case 14: Block __import__() dynamic imports + * Prevents runtime import injection + */ + it('blocks __import__() for dynamic imports', () => { + const unsafeCode = ` +os_module = __import__('os') + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings[0].message).toContain('__import__'); + }); + }); + + describe('error message formatting', () => { + /** + * Test Case 15: Error messages include line numbers + * Helps learners locate issues + */ + it('includes line numbers in error messages', () => { + const unsafeCode = ` +rover.forward(100) +import os +rover.backward(50) + `.trim(); + + const result = service.analyze(unsafeCode); + + expect(result.isValid).toBe(false); + expect(result.findings[0].line).toBe(2); + }); + + /** + * Test Case 16: Formatted error messages are readable + * Test the formatErrorMessage helper + */ + it('formats error messages for display to learners', () => { + const unsafeCode = ` +import os + `.trim(); + + const result = service.analyze(unsafeCode); + const formattedMessage = service.formatErrorMessage(result.findings); + + expect(formattedMessage).toContain('violation'); + expect(formattedMessage).toContain('line'); + expect(formattedMessage).toContain('os'); + }); + + /** + * Test Case 17: No errors for valid code + * Verify success message + */ + it('returns success message for valid code', () => { + const safeCode = 'rover.forward(100)'; + + const result = service.analyze(safeCode); + const formattedMessage = service.formatErrorMessage(result.findings); + + expect(formattedMessage).toContain('passed'); + }); + }); + + describe('AST analyzer direct tests', () => { + /** + * Test Case 18: AST analyzer catches imports + * Direct test of analyzer function + */ + it('AST analyzer detects disallowed imports', () => { + const unsafeCode = 'import subprocess'; + + const findings = analyzeCodeForAllowlist(unsafeCode); + + expect(findings).toHaveLength(1); + expect(findings[0].ruleId).toBe('disallowed-import'); + }); + + /** + * Test Case 19: AST analyzer catches function calls + * Direct test of function call detection + */ + it('AST analyzer detects non-rover function calls', () => { + const unsafeCode = 'rover.hack_system()'; + + const findings = analyzeCodeForAllowlist(unsafeCode); + + expect(findings).toHaveLength(1); + expect(findings[0].ruleId).toBe('disallowed-function'); + }); + + /** + * Test Case 20: AST analyzer returns empty for safe code + * Verify no false positives + */ + it('AST analyzer returns empty array for safe code', () => { + const safeCode = 'rover.forward(100)'; + + const findings = analyzeCodeForAllowlist(safeCode); + + expect(findings).toHaveLength(0); + }); + }); +}); diff --git a/mission-authoring/src/__tests__/unit/anonymous-auth.test.ts b/mission-authoring/src/__tests__/unit/anonymous-auth.test.ts new file mode 100644 index 0000000..c3d2ad0 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/anonymous-auth.test.ts @@ -0,0 +1,170 @@ +/** + * Unit Tests: Anonymous Authentication Service + * + * @jest-environment jsdom + */ + +import { + getOrCreateSession, + getCurrentSessionId, + clearSession, + isStorageAvailable, + getShortSessionId, +} from '@/lib/anonymous-auth'; + +// Mock localStorage +const localStorageMock = (() => { + let store: Record = {}; + + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { + store[key] = value; + }, + removeItem: (key: string) => { + delete store[key]; + }, + clear: () => { + store = {}; + }, + }; +})(); + +Object.defineProperty(window, 'localStorage', { + value: localStorageMock, +}); + +describe('Anonymous Authentication', () => { + beforeEach(() => { + localStorageMock.clear(); + }); + + describe('getOrCreateSession', () => { + it('creates a new session if none exists', () => { + const session = getOrCreateSession(); + + expect(session).toHaveProperty('sessionId'); + expect(session).toHaveProperty('createdAt'); + expect(session.sessionId).toHaveLength(21); // nanoid default length + expect(new Date(session.createdAt).getTime()).toBeGreaterThan(0); + }); + + it('retrieves existing session if available', () => { + const firstSession = getOrCreateSession(); + const secondSession = getOrCreateSession(); + + expect(secondSession.sessionId).toBe(firstSession.sessionId); + expect(secondSession.createdAt).toBe(firstSession.createdAt); + }); + + it('includes browser fingerprint', () => { + const session = getOrCreateSession(); + + expect(session.fingerprint).toBeDefined(); + expect(typeof session.fingerprint).toBe('string'); + expect(session.fingerprint!.length).toBeGreaterThan(0); + }); + + it('handles corrupted localStorage gracefully', () => { + // Corrupt the stored data + localStorageMock.setItem('mars-rover-session-id', 'invalid-json'); + + const session = getOrCreateSession(); + + expect(session).toHaveProperty('sessionId'); + expect(session.sessionId).toHaveLength(21); + }); + }); + + describe('getCurrentSessionId', () => { + it('returns null if no session exists', () => { + const sessionId = getCurrentSessionId(); + + expect(sessionId).toBeNull(); + }); + + it('returns sessionId if session exists', () => { + const session = getOrCreateSession(); + const sessionId = getCurrentSessionId(); + + expect(sessionId).toBe(session.sessionId); + }); + + it('handles corrupted data gracefully', () => { + localStorageMock.setItem('mars-rover-session-id', 'invalid'); + + const sessionId = getCurrentSessionId(); + + expect(sessionId).toBeNull(); + }); + }); + + describe('clearSession', () => { + it('removes session from localStorage', () => { + getOrCreateSession(); + expect(getCurrentSessionId()).not.toBeNull(); + + clearSession(); + + expect(getCurrentSessionId()).toBeNull(); + }); + + it('allows creating new session after clearing', () => { + const firstSession = getOrCreateSession(); + clearSession(); + const secondSession = getOrCreateSession(); + + expect(secondSession.sessionId).not.toBe(firstSession.sessionId); + }); + }); + + describe('isStorageAvailable', () => { + it('returns true when localStorage is available', () => { + expect(isStorageAvailable()).toBe(true); + }); + }); + + describe('getShortSessionId', () => { + it('returns first 8 characters of session ID', () => { + const fullId = 'abc123def456ghi789jkl'; + const shortId = getShortSessionId(fullId); + + expect(shortId).toBe('abc123de'); + expect(shortId).toHaveLength(8); + }); + + it('handles short IDs gracefully', () => { + const shortId = getShortSessionId('abc'); + + expect(shortId).toBe('abc'); + }); + }); + + describe('Session ID uniqueness', () => { + it('generates unique session IDs', () => { + clearSession(); + const session1 = getOrCreateSession(); + + clearSession(); + const session2 = getOrCreateSession(); + + expect(session1.sessionId).not.toBe(session2.sessionId); + }); + }); + + describe('Session ID collision resistance', () => { + it('uses nanoid with sufficient entropy', () => { + // Generate multiple sessions and ensure no duplicates + const sessionIds = new Set(); + + for (let i = 0; i < 100; i++) { + clearSession(); + const session = getOrCreateSession(); + sessionIds.add(session.sessionId); + } + + // All IDs should be unique + expect(sessionIds.size).toBe(100); + }); + }); +}); diff --git a/mission-authoring/src/__tests__/unit/confirmation-response.test.ts b/mission-authoring/src/__tests__/unit/confirmation-response.test.ts new file mode 100644 index 0000000..d3d342a --- /dev/null +++ b/mission-authoring/src/__tests__/unit/confirmation-response.test.ts @@ -0,0 +1,192 @@ +/** + * Unit Tests for Task 57: Queue Position Confirmation + * + * User Story 54: As a learner I want to receive a confirmation with my queue + * position when my mission is accepted so that I know my mission will be executed. + * + * Test Coverage: + * - Queue position calculation (number of missions ahead + 1) + * - Estimated wait time calculation (position * 90 seconds) + * - Edge cases (empty queue, first position) + * + * Testing Strategy: + * - Unit tests using Jest mocks + * - Repository layer is mocked to isolate service logic + * - Tests verify the response structure, not the database queries + * + * Queue Position Algorithm: + * queuePosition = (count of queued missions in same yard submitted before this one) + 1 + * + * Estimated Wait Algorithm: + * estimatedWait = (queuePosition - 1) * 90 seconds + * Assumption: Average mission execution time is 90 seconds + */ + +import { MissionService } from '@/core/application/services/MissionService'; +import { IMissionRepository } from '@/core/domain/repositories/IMissionRepository'; +import { Mission } from '@/core/domain/entities/Mission'; +import { CreateMissionDto } from '@/infrastructure/validation/schemas'; + +describe('queue confirmation', () => { + // Mock repository - simulates database without actual DB calls + let mockRepository: jest.Mocked; + + // Service under test - uses mocked repository + let service: MissionService; + + /** + * Setup before each test + * Creates fresh mock and service instances to ensure test isolation + */ + beforeEach(() => { + // Create mock implementation of IMissionRepository + // All methods are Jest mocks that can be configured per test + mockRepository = { + create: jest.fn(), + findById: jest.fn(), + findBySessionId: jest.fn(), + getQueuedMissions: jest.fn(), + update: jest.fn(), + getQueueLength: jest.fn(), + } as jest.Mocked; + + // Instantiate service with mocked repository + // This allows us to test service logic without database + service = new MissionService(mockRepository); + }); + + /** + * Test Case 1: Confirmation response includes queue position and estimated wait + * + * Scenario: Learner submits mission when 2 other missions are ahead in queue + * Expected: Response includes queuePosition=3 and estimatedWait=180 seconds + * + * Business Logic: + * - queuePosition = 3 (third in line) + * - estimatedWait = (3-1) * 90 = 180 seconds = 3 minutes + * + * This is the primary happy path test for User Story 54 + */ + it('confirmation response includes queue position and estimated wait', async () => { + // Arrange: Create valid mission submission DTO + const dto: CreateMissionDto = { + yardId: 'uct-rover-1', + sessionId: 'test-session-123', + code: 'rover.forward(100)', + challengeId: 'M1-FORWARD', + }; + + // Mock the repository response - simulate mission with queue position + const mockMission: Mission = { + id: 'mission-123', + yardId: 'uct-rover-1', + sessionId: 'test-session-123', + code: 'rover.forward(100)', + challengeId: 'M1-FORWARD', + status: 'queued', + submittedAt: new Date().toISOString(), + queuePosition: 3, // Third in queue + estimatedWait: 180, // (3-1) * 90 seconds = 180 seconds = 3 minutes + }; + + // Configure mock to return our test mission + mockRepository.create.mockResolvedValue(mockMission); + + // Act: Submit mission via service + const result = await service.submitMission(dto); + + // Assert: Verify response structure and values + expect(result.success).toBe(true); + expect(result.mission).toBeDefined(); + expect(result.mission?.queuePosition).toBe(3); + expect(result.mission?.estimatedWait).toBe(180); + }); + + /** + * Test Case 2: Queue position 1 has zero estimated wait + * + * Scenario: Learner's mission is next to execute (first in queue) + * Expected: queuePosition=1 and estimatedWait=0 seconds + * + * Business Logic: + * - queuePosition = 1 (no missions ahead) + * - estimatedWait = (1-1) * 90 = 0 seconds (ready to execute immediately) + * + * This tests the edge case where the mission can execute right away + */ + it('queue position 1 has zero estimated wait', async () => { + // Arrange: Mission submission without challengeId (optional field) + const dto: CreateMissionDto = { + yardId: 'uct-rover-1', + sessionId: 'test-session-456', + code: 'rover.forward(50)', + }; + + // Mock mission that's first in queue + const mockMission: Mission = { + id: 'mission-456', + yardId: 'uct-rover-1', + sessionId: 'test-session-456', + code: 'rover.forward(50)', + status: 'queued', + submittedAt: new Date().toISOString(), + queuePosition: 1, // First in queue + estimatedWait: 0, // No wait time - ready to execute + }; + + mockRepository.create.mockResolvedValue(mockMission); + + // Act: Submit mission + const result = await service.submitMission(dto); + + // Assert: Verify first position with zero wait + expect(result.success).toBe(true); + expect(result.mission?.queuePosition).toBe(1); + expect(result.mission?.estimatedWait).toBe(0); + }); + + /** + * Test Case 3: Handles empty queue correctly + * + * Scenario: Learner submits to a yard with no existing queued missions + * Expected: Mission becomes first in queue (position 1, wait 0) + * + * Business Logic: + * - Different yardId (uct-rover-2) to test multi-yard isolation + * - Empty queue = queuePosition 1 + * - No missions ahead = estimatedWait 0 + * + * This verifies that queue calculations work correctly when starting fresh + * and that different yards maintain separate queues + */ + it('handles empty queue correctly', async () => { + // Arrange: Submit to different yard (uct-rover-2) with empty queue + const dto: CreateMissionDto = { + yardId: 'uct-rover-2', // Different yard from previous tests + sessionId: 'test-session-789', + code: 'rover.turn_left(90)', // Different rover command + }; + + // Mock mission in empty queue + const mockMission: Mission = { + id: 'mission-789', + yardId: 'uct-rover-2', + sessionId: 'test-session-789', + code: 'rover.turn_left(90)', + status: 'queued', + submittedAt: new Date().toISOString(), + queuePosition: 1, // First and only mission in this yard's queue + estimatedWait: 0, // No wait when queue is empty + }; + + mockRepository.create.mockResolvedValue(mockMission); + + // Act: Submit to empty queue + const result = await service.submitMission(dto); + + // Assert: Verify correct handling of empty queue + expect(result.success).toBe(true); + expect(result.mission?.queuePosition).toBe(1); + expect(result.mission?.estimatedWait).toBe(0); + }); +}); diff --git a/mission-authoring/src/__tests__/unit/editor.test.tsx b/mission-authoring/src/__tests__/unit/editor.test.tsx new file mode 100644 index 0000000..9ff31d1 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/editor.test.tsx @@ -0,0 +1,3 @@ +describe('editor scaffold', () => { + it.todo('Task 20: editor renders and accepts learner input'); +}); diff --git a/mission-authoring/src/__tests__/unit/execution-timeout.test.ts b/mission-authoring/src/__tests__/unit/execution-timeout.test.ts new file mode 100644 index 0000000..f64a8fa --- /dev/null +++ b/mission-authoring/src/__tests__/unit/execution-timeout.test.ts @@ -0,0 +1,3 @@ +describe('execution timeout scaffold', () => { + it.todo('Task 75: execution timeout fires correctly'); +}); diff --git a/mission-authoring/src/__tests__/unit/firebase-admin.test.ts b/mission-authoring/src/__tests__/unit/firebase-admin.test.ts new file mode 100644 index 0000000..3153126 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/firebase-admin.test.ts @@ -0,0 +1,103 @@ +describe('initializeFirebaseAdmin', () => { + const originalEnv = process.env; + + async function importFirebaseAdmin() { + return import('@/infrastructure/persistence/firebase-admin'); + } + + beforeEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + process.env = { ...originalEnv }; + + delete process.env.FIREBASE_PROJECT_ID; + delete process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID; + delete process.env.REACT_APP_FIREBASE_PROJECT_ID; + delete process.env.FIREBASE_CLIENT_EMAIL; + delete process.env.FIREBASE_PRIVATE_KEY; + }); + + afterAll(() => { + process.env = originalEnv; + }); + + it('falls back to REACT_APP_FIREBASE_PROJECT_ID when FIREBASE_PROJECT_ID is unset', async () => { + const cert = jest.fn((config) => config); + const initializeApp = jest.fn(() => ({ name: 'test-app' })); + + process.env.REACT_APP_FIREBASE_PROJECT_ID = 'legacy-project-id'; + process.env.FIREBASE_CLIENT_EMAIL = 'test@test.iam.gserviceaccount.com'; + process.env.FIREBASE_PRIVATE_KEY = 'private-key'; + + jest.doMock('firebase-admin/app', () => ({ + cert, + getApps: jest.fn(() => []), + initializeApp, + })); + + jest.doMock('firebase-admin/firestore', () => ({ + getFirestore: jest.fn(), + })); + + const { initializeFirebaseAdmin } = await importFirebaseAdmin(); + + initializeFirebaseAdmin(); + + expect(cert).toHaveBeenCalledWith({ + projectId: 'legacy-project-id', + clientEmail: 'test@test.iam.gserviceaccount.com', + privateKey: 'private-key', + }); + expect(initializeApp).toHaveBeenCalledTimes(1); + }); + + it('trims whitespace and surrounding quotes from Firebase Admin env values', async () => { + const cert = jest.fn((config) => config); + const initializeApp = jest.fn(() => ({ name: 'test-app' })); + + process.env.FIREBASE_PROJECT_ID = ' "quoted-project" '; + process.env.FIREBASE_CLIENT_EMAIL = ' "test@test.iam.gserviceaccount.com" '; + process.env.FIREBASE_PRIVATE_KEY = ' "-----BEGIN PRIVATE KEY-----\\nabc\\n-----END PRIVATE KEY-----\\n" '; + + jest.doMock('firebase-admin/app', () => ({ + cert, + getApps: jest.fn(() => []), + initializeApp, + })); + + jest.doMock('firebase-admin/firestore', () => ({ + getFirestore: jest.fn(), + })); + + const { initializeFirebaseAdmin } = await importFirebaseAdmin(); + + initializeFirebaseAdmin(); + + expect(cert).toHaveBeenCalledWith({ + projectId: 'quoted-project', + clientEmail: 'test@test.iam.gserviceaccount.com', + privateKey: '-----BEGIN PRIVATE KEY-----\nabc\n-----END PRIVATE KEY-----\n', + }); + }); + + it('throws a clear error when Firebase Admin credentials are missing', async () => { + jest.doMock('firebase-admin/app', () => ({ + cert: jest.fn(), + getApps: jest.fn(() => []), + initializeApp: jest.fn(), + })); + + jest.doMock('firebase-admin/firestore', () => ({ + getFirestore: jest.fn(), + })); + + const { initializeFirebaseAdmin } = await importFirebaseAdmin(); + + expect(() => initializeFirebaseAdmin()).toThrow( + 'Missing Firebase Admin environment variables: FIREBASE_PROJECT_ID, FIREBASE_CLIENT_EMAIL, FIREBASE_PRIVATE_KEY.' + ); + expect(() => initializeFirebaseAdmin()).toThrow( + 'Client-side Firebase config such as NEXT_PUBLIC_FIREBASE_* or REACT_APP_FIREBASE_* is not enough on its own.' + ); + }); +}); diff --git a/mission-authoring/src/__tests__/unit/getLearnerID.test.ts b/mission-authoring/src/__tests__/unit/getLearnerID.test.ts new file mode 100644 index 0000000..a08e20c --- /dev/null +++ b/mission-authoring/src/__tests__/unit/getLearnerID.test.ts @@ -0,0 +1,123 @@ +/** + * Unit Tests: getLearnerID Utility + * + * @jest-environment jsdom + */ + +import { getLearnerID, clearLearnerID, hasLearnerID } from '@/lib/getLearnerID'; + +// Mock localStorage +const localStorageMock = (() => { + let store: Record = {}; + + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { + store[key] = value; + }, + removeItem: (key: string) => { + delete store[key]; + }, + clear: () => { + store = {}; + }, + }; +})(); + +Object.defineProperty(window, 'localStorage', { + value: localStorageMock, +}); + +describe('getLearnerID Utility', () => { + beforeEach(() => { + localStorageMock.clear(); + }); + + describe('getLearnerID', () => { + it('generates a new ID if none exists', () => { + const id = getLearnerID(); + + expect(id).toBeDefined(); + expect(typeof id).toBe('string'); + expect(id.length).toBe(21); // nanoid default length + }); + + it('reuses existing ID if found in localStorage', () => { + const firstId = getLearnerID(); + const secondId = getLearnerID(); + + expect(secondId).toBe(firstId); + }); + + it('stores ID in localStorage', () => { + const id = getLearnerID(); + const stored = localStorage.getItem('mars-rover-learner-id'); + + expect(stored).toBe(id); + }); + + it('generates unique IDs on each call after clearing', () => { + const id1 = getLearnerID(); + clearLearnerID(); + const id2 = getLearnerID(); + + expect(id1).not.toBe(id2); + }); + }); + + describe('clearLearnerID', () => { + it('removes ID from localStorage', () => { + getLearnerID(); + expect(hasLearnerID()).toBe(true); + + clearLearnerID(); + + expect(hasLearnerID()).toBe(false); + }); + }); + + describe('hasLearnerID', () => { + it('returns false when no ID exists', () => { + expect(hasLearnerID()).toBe(false); + }); + + it('returns true when ID exists', () => { + getLearnerID(); + expect(hasLearnerID()).toBe(true); + }); + + it('returns false after clearing', () => { + getLearnerID(); + clearLearnerID(); + expect(hasLearnerID()).toBe(false); + }); + }); + + describe('ID uniqueness', () => { + it('generates collision-resistant IDs', () => { + const ids = new Set(); + + // Generate 100 IDs + for (let i = 0; i < 100; i++) { + clearLearnerID(); + const id = getLearnerID(); + ids.add(id); + } + + // All should be unique + expect(ids.size).toBe(100); + }); + }); + + describe('ID persistence', () => { + it('persists across multiple calls', () => { + const originalId = getLearnerID(); + + // Call multiple times + const calls = Array.from({ length: 10 }, () => getLearnerID()); + + // All should return the same ID + expect(calls.every((id) => id === originalId)).toBe(true); + }); + }); +}); diff --git a/mission-authoring/src/__tests__/unit/heartbeat.test.ts b/mission-authoring/src/__tests__/unit/heartbeat.test.ts new file mode 100644 index 0000000..8e7e87e --- /dev/null +++ b/mission-authoring/src/__tests__/unit/heartbeat.test.ts @@ -0,0 +1,3 @@ +describe('heartbeat scaffold', () => { + it.todo('Task 68: heartbeat detects disconnection'); +}); diff --git a/mission-authoring/src/__tests__/unit/rover-movement.test.ts b/mission-authoring/src/__tests__/unit/rover-movement.test.ts new file mode 100644 index 0000000..d7bb706 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/rover-movement.test.ts @@ -0,0 +1,3 @@ +describe('rover movement scaffold', () => { + it.todo('Task 31: rover movement calculations are correct'); +}); diff --git a/mission-authoring/src/__tests__/unit/sandbox-disallowed-commands.test.ts b/mission-authoring/src/__tests__/unit/sandbox-disallowed-commands.test.ts new file mode 100644 index 0000000..8ee9d70 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/sandbox-disallowed-commands.test.ts @@ -0,0 +1,3 @@ +describe('sandbox disallowed commands scaffold', () => { + it.todo('Task 76: disallowed commands raise exceptions in the sandbox'); +}); diff --git a/mission-authoring/src/__tests__/unit/validation.test.ts b/mission-authoring/src/__tests__/unit/validation.test.ts new file mode 100644 index 0000000..15e204f --- /dev/null +++ b/mission-authoring/src/__tests__/unit/validation.test.ts @@ -0,0 +1,201 @@ +/** + * Unit Tests for Mission Schema Validation (Task 39) + * + * Tests validation rules for mission submission. + * Ensures data integrity before persistence. + */ + +import { validateMission, createMissionSchema } from '@/infrastructure/validation/schemas'; + +describe('Mission Schema Validation', () => { + describe('validateMission', () => { + it('should accept valid mission data', () => { + const validData = { + yardId: 'uct-rover-1', + learnerId: 'learner-123', + sessionId: 'test-session-123', + name: 'Test Mission', + code: 'rover.forward(100)\nrover.wait(2)', + challengeId: 'M1-FORWARD', + }; + + const result = validateMission(validData); + + expect(result.success).toBe(true); + expect(result.data).toEqual(validData); + expect(result.errors).toBeUndefined(); + }); + + it('should accept mission without optional challengeId', () => { + const validData = { + yardId: 'yard-1', + learnerId: 'learner-456', + sessionId: 'session-456', + name: 'Turn Left Mission', + code: 'rover.turn_left(50)', // Updated to use approved command + }; + + const result = validateMission(validData); + + expect(result.success).toBe(true); + expect(result.data?.challengeId).toBeUndefined(); + }); + + it('should reject empty yardId', () => { + const invalidData = { + yardId: '', + sessionId: 'session-123', + code: 'rover.forward(100)', + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors).toContain('yardId: Yard ID is required'); + }); + + it('should reject yardId with invalid characters', () => { + const invalidData = { + yardId: 'yard@#$%', + sessionId: 'session-123', + code: 'rover.forward(100)', + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors?.[0]).toContain('Yard ID must contain only alphanumeric characters'); + }); + + it('should reject empty sessionId', () => { + const invalidData = { + yardId: 'yard-1', + sessionId: '', + code: 'rover.forward(100)', + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors).toContain('sessionId: Session ID is required'); + }); + + it('should reject empty code', () => { + const invalidData = { + yardId: 'yard-1', + sessionId: 'session-123', + code: '', + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors).toContain('code: Code cannot be empty'); + }); + + it('should reject code with only whitespace', () => { + const invalidData = { + yardId: 'yard-1', + sessionId: 'session-123', + code: ' \n\t ', + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors).toContain('code: Code cannot be only whitespace'); + }); + + it('should reject code exceeding maximum length', () => { + const invalidData = { + yardId: 'yard-1', + learnerId: 'learner-123', + sessionId: 'session-123', + name: 'Too Long Mission', + code: 'a'.repeat(10001), + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors?.[0]).toContain('Code exceeds maximum length'); + }); + + it('should reject yardId exceeding maximum length', () => { + const invalidData = { + yardId: 'a'.repeat(51), + sessionId: 'session-123', + code: 'rover.forward(100)', + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors).toContain('yardId: Yard ID too long'); + }); + + it('should reject sessionId exceeding maximum length', () => { + const invalidData = { + yardId: 'yard-1', + sessionId: 'a'.repeat(101), + code: 'rover.forward(100)', + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors).toContain('sessionId: Session ID too long'); + }); + + it('should reject missing required fields', () => { + const invalidData = { + yardId: 'yard-1', + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors?.length).toBeGreaterThan(0); + }); + + it('should return multiple errors for multiple invalid fields', () => { + const invalidData = { + yardId: '', + sessionId: '', + code: '', + }; + + const result = validateMission(invalidData); + + expect(result.success).toBe(false); + expect(result.errors?.length).toBeGreaterThanOrEqual(3); + }); + }); + + describe('createMissionSchema', () => { + it('should parse valid data with Zod', () => { + const validData = { + yardId: 'rover-yard-1', + learnerId: 'learner_abc123', + sessionId: 'sess_abc123', + name: 'Zod Parse Mission', + code: 'rover.forward(100)\nrover.stop()', + challengeId: 'LEVEL-1', + }; + + const parsed = createMissionSchema.parse(validData); + + expect(parsed).toEqual(validData); + }); + + it('should throw ZodError for invalid data', () => { + const invalidData = { + yardId: 123, + sessionId: null, + code: '', + }; + + expect(() => createMissionSchema.parse(invalidData)).toThrow(); + }); + }); +}); diff --git a/mission-authoring/src/__tests__/unit/video-player.test.tsx b/mission-authoring/src/__tests__/unit/video-player.test.tsx new file mode 100644 index 0000000..cfd8667 --- /dev/null +++ b/mission-authoring/src/__tests__/unit/video-player.test.tsx @@ -0,0 +1,321 @@ +/** + * Unit Tests for Task 107: VideoPlayer Component + * + * User Story 102: As a learner I want to view and download my mission video + * on the platform so that I can see exactly what my code caused the rover to do. + * + * Test Coverage: + * - Video player rendering with GCS URLs + * - YouTube embed rendering + * - Component props and configuration + * - Loading and error states + * - No video placeholder + * + * @jest-environment jsdom + */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { VideoPlayer } from '@/components/mission/VideoPlayer'; + +describe('VideoPlayer Component', () => { + const mockMissionId = 'test-mission-123'; + + beforeEach(() => { + // Reset any mocks before each test + jest.clearAllMocks(); + }); + + describe('No Video Available', () => { + /** + * Test Case 1: Shows placeholder when no video URL provided + */ + it('shows placeholder when no video URLs provided', () => { + render(); + + expect(screen.getByText('Video not available yet')).toBeInTheDocument(); + expect( + screen.getByText('Videos are generated after mission execution completes') + ).toBeInTheDocument(); + }); + + /** + * Test Case 2: Shows camera icon in placeholder + */ + it('displays camera icon in placeholder', () => { + const { container } = render(); + + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); + }); + + describe('Direct Video Playback (GCS URL)', () => { + /** + * Test Case 3: Renders video element with correct source + */ + it('renders video element with GCS URL', () => { + const videoUrl = 'https://storage.googleapis.com/test-bucket/mission-123.mp4'; + + const { container } = render( + + ); + + const videoElement = container.querySelector('video'); + expect(videoElement).toBeInTheDocument(); + expect(videoElement).toHaveAttribute('src', videoUrl); + expect(videoElement).toHaveAttribute('controls'); + }); + + /** + * Test Case 4: Shows mission title + */ + it('displays custom title', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + const title = 'My Mission Video'; + + render( + + ); + + expect(screen.getByText(title)).toBeInTheDocument(); + }); + + /** + * Test Case 5: Shows mission ID + */ + it('displays mission ID', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + + render(); + + expect(screen.getByText(mockMissionId)).toBeInTheDocument(); + }); + + /** + * Test Case 6: Download button is present by default + */ + it('shows download button by default', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + + render(); + + expect(screen.getByRole('button', { name: /download/i })).toBeInTheDocument(); + }); + + /** + * Test Case 7: Can hide download button + */ + it('hides download button when showDownload is false', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + + render( + + ); + + expect(screen.queryByRole('button', { name: /download/i })).not.toBeInTheDocument(); + }); + + /** + * Test Case 8: Shows default title when not provided + */ + it('uses default title "Mission Video" when title not provided', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + + render(); + + expect(screen.getByText('Mission Video')).toBeInTheDocument(); + }); + }); + + describe('YouTube Embed', () => { + /** + * Test Case 9: Renders YouTube iframe with correct video ID + */ + it('renders YouTube iframe with video ID from youtube.com URL', () => { + const youtubeUrl = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; + + const { container } = render( + + ); + + const iframe = container.querySelector('iframe'); + expect(iframe).toBeInTheDocument(); + expect(iframe).toHaveAttribute( + 'src', + 'https://www.youtube.com/embed/dQw4w9WgXcQ' + ); + }); + + /** + * Test Case 10: Handles youtu.be short URLs + */ + it('extracts video ID from youtu.be URL', () => { + const youtubeUrl = 'https://youtu.be/dQw4w9WgXcQ'; + + const { container } = render( + + ); + + const iframe = container.querySelector('iframe'); + expect(iframe).toBeInTheDocument(); + expect(iframe).toHaveAttribute( + 'src', + 'https://www.youtube.com/embed/dQw4w9WgXcQ' + ); + }); + + /** + * Test Case 11: Shows YouTube link + */ + it('displays link to open in YouTube', () => { + const youtubeUrl = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; + + render(); + + const link = screen.getByText('Open in YouTube →'); + expect(link).toHaveAttribute('href', youtubeUrl); + expect(link).toHaveAttribute('target', '_blank'); + expect(link).toHaveAttribute('rel', 'noopener noreferrer'); + }); + + /** + * Test Case 12: YouTube preferred over GCS URL + */ + it('prefers YouTube embed over direct video when both provided', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + const youtubeUrl = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; + + const { container } = render( + + ); + + // Should render iframe, not video element + expect(container.querySelector('iframe')).toBeInTheDocument(); + expect(container.querySelector('video')).not.toBeInTheDocument(); + }); + + /** + * Test Case 13: Handles embed URLs + */ + it('extracts video ID from embed URL', () => { + const youtubeUrl = 'https://www.youtube.com/embed/testVideoId'; + + const { container } = render( + + ); + + const iframe = container.querySelector('iframe'); + expect(iframe).toHaveAttribute( + 'src', + 'https://www.youtube.com/embed/testVideoId' + ); + }); + }); + + describe('Component Props', () => { + /** + * Test Case 14: Applies custom className + */ + it('applies custom className to container', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + const customClass = 'my-custom-class'; + + const { container } = render( + + ); + + const playerContainer = container.firstChild as HTMLElement; + expect(playerContainer).toHaveClass(customClass); + }); + + /** + * Test Case 15: Accepts title prop + */ + it('accepts and displays custom title prop', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + const customTitle = 'Custom Mission Title'; + + render( + + ); + + expect(screen.getByText(customTitle)).toBeInTheDocument(); + }); + + /** + * Test Case 16: Accepts showDownload prop + */ + it('respects showDownload prop', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + + const { rerender } = render( + + ); + + expect(screen.getByRole('button', { name: /download/i })).toBeInTheDocument(); + + rerender( + + ); + + expect(screen.queryByRole('button', { name: /download/i })).not.toBeInTheDocument(); + }); + }); + + describe('Component Structure', () => { + /** + * Test Case 17: Video element has correct attributes + */ + it('video element has controls and preload metadata', () => { + const videoUrl = 'https://storage.googleapis.com/test/video.mp4'; + + const { container } = render( + + ); + + const videoElement = container.querySelector('video'); + expect(videoElement).toHaveAttribute('controls'); + expect(videoElement).toHaveAttribute('preload', 'metadata'); + }); + + /** + * Test Case 18: YouTube iframe has correct attributes + */ + it('YouTube iframe has allowFullScreen attribute', () => { + const youtubeUrl = 'https://www.youtube.com/watch?v=test123'; + + const { container } = render( + + ); + + const iframe = container.querySelector('iframe'); + expect(iframe).toHaveAttribute('allowFullScreen'); + expect(iframe).toHaveAttribute('title'); + }); + }); +}); diff --git a/mission-authoring/src/app/api/challenges/[challengeId]/hint/route.ts b/mission-authoring/src/app/api/challenges/[challengeId]/hint/route.ts new file mode 100644 index 0000000..2e29d33 --- /dev/null +++ b/mission-authoring/src/app/api/challenges/[challengeId]/hint/route.ts @@ -0,0 +1,48 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { z } from 'zod'; +import { getFirestoreClient } from '@/lib/firebase'; +import { FirestoreChallengeRepository } from '@/infrastructure/persistence/FirestoreChallengeRepository'; +import { FirestoreLearnerRepository } from '@/infrastructure/persistence/FirestoreLearnerRepository'; + +const getHintSchema = z.object({ + learnerId: z.string().min(1, 'Learner ID required'), + hintLevel: z.number().int().min(1).max(3), +}); + +export async function PATCH(request: NextRequest, { params }: { params: Promise<{ challengeId?: string }> }) { + try { + const { challengeId } = await params; + if (!challengeId) { + return NextResponse.json({ success: false, error: 'Challenge ID required' }, { status: 400 }); + } + + const body = await request.json(); + const validation = getHintSchema.safeParse(body); + if (!validation.success) { + return NextResponse.json({ success: false, error: 'Invalid request', details: validation.error.errors }, { status: 400 }); + } + + const payload = validation.data; + + const db = getFirestoreClient(); + const challengeRepository = new FirestoreChallengeRepository(db); + + const challenge = await challengeRepository.getById(challengeId); + if (!challenge) { + return NextResponse.json({ success: false, error: 'Challenge not found' }, { status: 404 }); + } + + const hint = challenge.hints.find((h) => h.level === payload.hintLevel); + if (!hint) { + return NextResponse.json({ success: false, error: 'Hint not available at that level' }, { status: 400 }); + } + + const learnerRepository = new FirestoreLearnerRepository(db); + await learnerRepository.recordHintUsage(payload.learnerId, challengeId, payload.hintLevel); + + return NextResponse.json({ success: true, hint: hint.text, level: hint.level, message: `Hint ${payload.hintLevel}/3 revealed` }); + } catch (error) { + console.error('❌ Hint retrieval error:', error); + return NextResponse.json({ success: false, error: 'Server error retrieving hint' }, { status: 500 }); + } +} diff --git a/mission-authoring/src/app/api/challenges/route.ts b/mission-authoring/src/app/api/challenges/route.ts new file mode 100644 index 0000000..e3b6d9f --- /dev/null +++ b/mission-authoring/src/app/api/challenges/route.ts @@ -0,0 +1,11 @@ +import { NextResponse } from 'next/server'; +import { CHALLENGES } from '@/data/challenges'; + +export async function GET() { + try { + return NextResponse.json({ success: true, challenges: CHALLENGES }); + } catch (error) { + console.error('❌ Failed to return challenges:', error); + return NextResponse.json({ success: false, error: 'Failed to load challenges' }, { status: 500 }); + } +} diff --git a/mission-authoring/src/app/api/challenges/submit/route.ts b/mission-authoring/src/app/api/challenges/submit/route.ts new file mode 100644 index 0000000..a52f0c2 --- /dev/null +++ b/mission-authoring/src/app/api/challenges/submit/route.ts @@ -0,0 +1,50 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { z } from 'zod'; +import { getFirestoreClient } from '@/lib/firebase'; +import { FirestoreLearnerRepository } from '@/infrastructure/persistence/FirestoreLearnerRepository'; +import { FirestoreChallengeRepository } from '@/infrastructure/persistence/FirestoreChallengeRepository'; +import { ChallengeSubmissionService } from '@/core/application/services/ChallengeSubmissionService'; + +const submitChallengeSchema = z.object({ + learnerId: z.string().min(1, 'Learner ID required'), + challengeId: z.string().min(1, 'Challenge ID required'), + simulatorOutput: z.object({ + success: z.boolean(), + shapes: z.array(z.any()), + consoleOutput: z.string().optional(), + errorMessage: z.string().optional(), + }), + hintsUsed: z.number().int().min(0).max(3).default(0), +}); + +type SubmitChallengeRequest = z.infer; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const validation = submitChallengeSchema.safeParse(body); + if (!validation.success) { + return NextResponse.json({ success: false, error: 'Invalid request', details: validation.error.errors }, { status: 400 }); + } + + const payload = validation.data as SubmitChallengeRequest; + + const db = getFirestoreClient(); + const learnerRepository = new FirestoreLearnerRepository(db); + const challengeRepository = new FirestoreChallengeRepository(db); + + const submissionService = new ChallengeSubmissionService(learnerRepository, challengeRepository); + + const result = await submissionService.submitChallenge({ + learnerId: payload.learnerId, + challengeId: payload.challengeId, + simulatorOutput: payload.simulatorOutput, + hintsUsed: payload.hintsUsed, + }); + + return NextResponse.json(result, { status: result.success ? 200 : 400 }); + } catch (error) { + console.error('❌ Challenge submission error:', error); + return NextResponse.json({ success: false, error: 'Server error during challenge submission', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 }); + } +} diff --git a/mission-authoring/src/app/api/leaderboard/route.ts b/mission-authoring/src/app/api/leaderboard/route.ts new file mode 100644 index 0000000..6efa7e7 --- /dev/null +++ b/mission-authoring/src/app/api/leaderboard/route.ts @@ -0,0 +1,81 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getFirestoreInstance } from '@/infrastructure/persistence/firebase-admin'; + +const LEARNERS = 'learners'; + +/** + * GET /api/leaderboard + * + * Server route — uses the Firebase ADMIN SDK (not the web SDK). The web SDK's + * gRPC/long-poll transport is unreliable in Node and was hanging this route for + * 30–80s; the Admin SDK is built for the server and is fast + stable. + */ +export async function GET(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const limit = Math.min(parseInt(searchParams.get('limit') || '100'), 500); + const offset = parseInt(searchParams.get('offset') || '0'); + const learnerId = searchParams.get('learnerId'); + + const db = getFirestoreInstance(); + + // Single-learner rank lookup + if (learnerId) { + const snap = await db.collection(LEARNERS).doc(learnerId).get(); + const data = snap.exists ? snap.data() : undefined; + const xp = (data?.xp as number) ?? 0; + + if (!data || xp === 0) { + return NextResponse.json({ + success: true, + learnerRank: null, + message: 'Learner not on leaderboard yet', + }); + } + + const ahead = await db.collection(LEARNERS).where('xp', '>', xp).count().get(); + return NextResponse.json({ + success: true, + learnerRank: { rank: ahead.data().count + 1, xp, level: (data.level as number) ?? 1 }, + }); + } + + // Leaderboard page (Firestore has no offset — fetch limit+offset, then slice) + const pageSnap = await db + .collection(LEARNERS) + .where('xp', '>', 0) + .orderBy('xp', 'desc') + .limit(limit + offset) + .get(); + + const leaderboard = pageSnap.docs.slice(offset).map((doc, index) => { + const data = doc.data(); + return { + rank: offset + index + 1, + learnerId: doc.id, + displayName: data.displayName || `Player ${doc.id.slice(0, 8)}`, + level: data.level ?? 1, + xp: data.xp ?? 0, + }; + }); + + const totalSnap = await db.collection(LEARNERS).where('xp', '>', 0).count().get(); + const total = totalSnap.data().count; + + return NextResponse.json({ + success: true, + leaderboard, + pagination: { limit, offset, total, hasMore: offset + limit < total }, + }); + } catch (error) { + console.error('❌ Leaderboard error:', error); + return NextResponse.json( + { + success: false, + error: 'Failed to retrieve leaderboard', + details: error instanceof Error ? error.message : 'Unknown error', + }, + { status: 500 } + ); + } +} diff --git a/mission-authoring/src/app/api/missions/route.ts b/mission-authoring/src/app/api/missions/route.ts new file mode 100644 index 0000000..33c0f81 --- /dev/null +++ b/mission-authoring/src/app/api/missions/route.ts @@ -0,0 +1,54 @@ +/** + * POST /api/missions — server-side mission submission (Tasks 40 & 41). + * + * Validates the payload (schema + command allowlist) before persisting, so + * submission can be trusted even if it bypasses the client UI. The client may + * still submit directly via the repository, but this endpoint is the safe path. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { validateMission } from '@/infrastructure/validation/schemas'; +import { getFirestoreInstance } from '@/infrastructure/persistence/firebase-admin'; +import { FirestoreMissionRepository } from '@/infrastructure/persistence/FirestoreMissionRepository'; +import { MissionService } from '@/core/application/services/MissionService'; + +export async function POST(request: NextRequest) { + let body: unknown; + try { + body = await request.json(); + } catch { + return NextResponse.json( + { success: false, error: 'Invalid JSON body' }, + { status: 500 } + ); + } + + // Phase 1 + 2: schema and command-allowlist validation + const validation = validateMission(body); + if (!validation.success || !validation.data) { + return NextResponse.json( + { success: false, error: 'Validation failed', details: validation.errors ?? [] }, + { status: 400 } + ); + } + + try { + const repository = new FirestoreMissionRepository(getFirestoreInstance()); + const service = new MissionService(repository); + const result = await service.submitMission(validation.data); + + if (!result.success || !result.mission) { + return NextResponse.json( + { success: false, error: result.error ?? 'Failed to submit mission' }, + { status: 500 } + ); + } + + return NextResponse.json({ success: true, mission: result.mission }, { status: 201 }); + } catch (error) { + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Unknown error' }, + { status: 500 } + ); + } +} diff --git a/mission-authoring/src/app/api/simulate/video/route.ts b/mission-authoring/src/app/api/simulate/video/route.ts new file mode 100644 index 0000000..c84fe20 --- /dev/null +++ b/mission-authoring/src/app/api/simulate/video/route.ts @@ -0,0 +1,39 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const YARD_SERVER_URL = process.env.YARD_SERVER_URL || 'http://localhost:8523'; + +export async function POST(request: NextRequest) { + const body = await request.json(); + + let yardResponse: Response; + try { + yardResponse = await fetch(`${YARD_SERVER_URL}/simulate/video`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(60_000), + }); + } catch (err) { + return NextResponse.json( + { error: 'Yard server unreachable', detail: String(err) }, + { status: 503 } + ); + } + + if (!yardResponse.ok) { + const text = await yardResponse.text(); + return NextResponse.json( + { error: 'Yard server error', detail: text }, + { status: yardResponse.status } + ); + } + + const videoBuffer = await yardResponse.arrayBuffer(); + return new NextResponse(videoBuffer, { + status: 200, + headers: { + 'Content-Type': 'video/mp4', + 'Content-Disposition': `attachment; filename="simulation.mp4"`, + }, + }); +} diff --git a/mission-authoring/src/app/challenges/page.tsx b/mission-authoring/src/app/challenges/page.tsx new file mode 100644 index 0000000..8127edc --- /dev/null +++ b/mission-authoring/src/app/challenges/page.tsx @@ -0,0 +1,744 @@ +/** + * Challenges Page + * + * Main challenge interface where learners: + * - Browse available challenges + * - Select and attempt challenges + * - View their progress + * - Access leaderboard + * - View earned badges + * + * Route: /challenges + */ + +'use client'; + +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useLearner } from '@/contexts/LearnerContext'; +import { Challenge } from '@/core/domain/entities/Challenge'; +import { LevelDisplay, LevelBadge } from '@/components/challenge/LevelDisplay'; +import { ChallengeCard, ChallengeList } from '@/components/challenge/ChallengeCard'; + +type TabType = 'available' | 'progress' | 'leaderboard' | 'badges'; + +export function ChallengesPage() { + const router = useRouter(); + const { learner, sessionId, loading: learnerLoading } = useLearner(); + const [activeTab, setActiveTab] = useState('available'); + const [challenges, setChallenges] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedChallenge, setSelectedChallenge] = useState(null); + const [showChallengeModal, setShowChallengeModal] = useState(false); + + // Load challenges on mount + useEffect(() => { + const loadChallenges = async () => { + try { + setLoading(true); + // TODO: Replace with actual API call when ready + // const response = await fetch('/api/challenges'); + // const data = await response.json(); + // setChallenges(data.challenges); + + // For now, use mock data + setChallenges(MOCK_CHALLENGES); + } catch (error) { + console.error('Failed to load challenges:', error); + } finally { + setLoading(false); + } + }; + + loadChallenges(); + }, []); + + if (learnerLoading || loading) { + return ( +

+ ); + } + + if (!learner) { + return ( +
+
+

Please log in to access challenges

+
+
+ ); + } + + // Filter challenges based on learner level + const availableChallenges = challenges.filter( + (c) => c.requiredLevel <= learner.level + ); + + const completedChallenges = challenges.filter((c) => + learner.challenges_completed.includes(c.id) + ); + + const inProgressChallenges = challenges.filter( + (c) => + !learner.challenges_completed.includes(c.id) && + learner.challenges_unlocked.includes(c.id) + ); + + const stats = { + total: challenges.length, + completed: completedChallenges.length, + inProgress: inProgressChallenges.length, + available: availableChallenges.length, + completionRate: Math.round((completedChallenges.length / challenges.length) * 100), + }; + + return ( +
+ {/* Header */} +
+
+
+

🎮 Challenges

+

+ Master rover programming by solving challenges +

+
+
+ + + {learner.xp} XP + +
+
+ + {/* Stats Grid */} +
+ + + + +
+ + {/* Tabs */} +
+
+ setActiveTab('available')} + icon="🔓" + /> + setActiveTab('progress')} + icon="⏳" + /> + setActiveTab('leaderboard')} + icon="🏆" + /> + setActiveTab('badges')} + icon="🏅" + /> +
+
+ + {/* Tab Content */} +
+ {activeTab === 'available' && ( + { + setSelectedChallenge(challenge); + setShowChallengeModal(true); + }} + /> + )} + + {activeTab === 'progress' && ( + + )} + + {activeTab === 'leaderboard' && ( + + )} + + {activeTab === 'badges' && ( + + )} +
+
+ + {/* Challenge Detail Modal */} + {showChallengeModal && selectedChallenge && ( + { + setShowChallengeModal(false); + setSelectedChallenge(null); + }} + onStart={() => { + // Navigate to code editor with challenge context + const params = new URLSearchParams({ + mode: 'challenge', + challengeId: selectedChallenge.id, + challengeTitle: selectedChallenge.title, + challengeDescription: selectedChallenge.description, + difficulty: selectedChallenge.difficulty, + xpReward: selectedChallenge.xpReward.toString(), + }); + router.push(`/mission?${params.toString()}`); + }} + /> + )} +
+ ); +} + +/** + * Tab Button Component + */ +function TabButton({ + label, + count, + active, + onClick, + icon, +}: { + label: string; + count?: number; + active: boolean; + onClick: () => void; + icon: string; +}) { + return ( + + ); +} + +/** + * Stat Card Component + */ +function StatCard({ + label, + value, + total, + unit, + icon, + color, +}: { + label: string; + value: number; + total?: number; + unit?: string; + icon: string; + color: 'blue' | 'green' | 'yellow' | 'purple'; +}) { + const bgColor = { + blue: 'bg-blue-900/20 border-blue-700', + green: 'bg-green-900/20 border-green-700', + yellow: 'bg-yellow-900/20 border-yellow-700', + purple: 'bg-purple-900/20 border-purple-700', + }; + + const textColor = { + blue: 'text-blue-400', + green: 'text-green-400', + yellow: 'text-yellow-400', + purple: 'text-purple-400', + }; + + return ( +
+
{icon}
+
{label}
+
+ {value} + {unit && {unit}} + {total && / {total}} +
+
+ ); +} + +/** + * Available Challenges Tab + */ +function AvailableChallengesTab({ + challenges, + completedIds, + onSelectChallenge, +}: { + challenges: Challenge[]; + completedIds: string[]; + onSelectChallenge: (challenge: Challenge) => void; +}) { + if (challenges.length === 0) { + return ( +
+

+ No challenges available at your current level. Level up to unlock more! +

+
+ ); + } + + // Group by difficulty + const byDifficulty = { + beginner: challenges.filter((c) => c.difficulty === 'beginner'), + intermediate: challenges.filter((c) => c.difficulty === 'intermediate'), + advanced: challenges.filter((c) => c.difficulty === 'advanced'), + }; + + return ( +
+ {byDifficulty.beginner.length > 0 && ( +
+

🟢 Beginner

+ ({ + ...c, + status: completedIds.includes(c.id) + ? ('completed' as const) + : ('unlocked' as const), + }))} + onSelectChallenge={onSelectChallenge} + /> +
+ )} + + {byDifficulty.intermediate.length > 0 && ( +
+

🟡 Intermediate

+ ({ + ...c, + status: completedIds.includes(c.id) + ? ('completed' as const) + : ('unlocked' as const), + }))} + onSelectChallenge={onSelectChallenge} + /> +
+ )} + + {byDifficulty.advanced.length > 0 && ( +
+

🔴 Advanced

+ ({ + ...c, + status: completedIds.includes(c.id) + ? ('completed' as const) + : ('unlocked' as const), + }))} + onSelectChallenge={onSelectChallenge} + /> +
+ )} +
+ ); +} + +/** + * Progress Tab + */ +function ProgressTab({ + challenges, + completedIds, + learnerStats, +}: { + challenges: Challenge[]; + completedIds: string[]; + learnerStats: { + totalSubmissions: number; + successfulSubmissions: number; + xp: number; + level: number; + }; +}) { + const successRate = + learnerStats.totalSubmissions > 0 + ? Math.round((learnerStats.successfulSubmissions / learnerStats.totalSubmissions) * 100) + : 0; + + return ( +
+ {/* Stats */} +
+
+
Total Submissions
+
+ {learnerStats.totalSubmissions} +
+
+
+
Success Rate
+
+ {successRate}% +
+
+
+
Current Level
+
+ {learnerStats.level} +
+
+
+ + {/* In Progress Challenges */} + {challenges.length > 0 ? ( +
+

Active Challenges

+ +
+ ) : ( +
+

No challenges in progress. Start a new one!

+
+ )} +
+ ); +} + +/** + * Leaderboard Tab (Simplified) + */ +function LeaderboardTab() { + return ( +
+ ); +} + +/** + * Badges Tab + */ +function BadgesTab({ + earnedBadges, + allBadges, +}: { + earnedBadges: string[]; + allBadges: typeof MOCK_BADGES; +}) { + return ( +
+ {/* Earned Badges */} + {earnedBadges.length > 0 && ( +
+

✨ Earned Badges ({earnedBadges.length})

+
+ {earnedBadges.map((badgeId) => { + const badge = allBadges.find((b) => b.id === badgeId); + if (!badge) return null; + + return ( +
+
{badge.icon}
+

{badge.name}

+

{badge.description}

+
+ ); + })} +
+
+ )} + + {/* Locked Badges */} +
+

+ 🔒 Locked Badges ({allBadges.length - earnedBadges.length}) +

+
+ {allBadges + .filter((b) => !earnedBadges.includes(b.id)) + .map((badge) => ( +
+
🔒
+

{badge.name}

+

{badge.description}

+
+ ))} +
+
+
+ ); +} + +/** + * Challenge Detail Modal + */ +function ChallengeDetailModal({ + challenge, + completed, + onClose, + onStart, +}: { + challenge: Challenge; + completed: boolean; + onClose: () => void; + onStart: () => void; +}) { + return ( +
+
+ {/* Header */} +
+
+

{challenge.title}

+
+ + {challenge.difficulty} + + 📂 {challenge.category} +
+
+ +
+ + {/* Description */} +
+

{challenge.description}

+
+ + {/* Rewards */} +
+
+
XP Reward
+
+{challenge.xpReward}
+
+
+
Space Rocks
+
+ {challenge.difficulty === 'beginner' + ? '+1' + : challenge.difficulty === 'intermediate' + ? '+2' + : '+3'} +
+
+ {challenge.badgeReward && ( +
+
Badge
+
🏆
+
+ )} +
+ + {/* Hints */} +
+

💡 Available Hints

+
+ {challenge.hints.map((hint, i) => ( +
+ Hint {i + 1}: {hint.text} +
+ ))} +
+
+ + {/* Actions */} +
+ + +
+
+
+ ); +} + +/** + * Mock data for development + */ +const MOCK_CHALLENGES: Challenge[] = [ + { + id: 'draw-line', + title: 'Draw a Line', + description: 'Draw a straight line from point A to point B', + category: 'shapes', + difficulty: 'beginner', + requiredLevel: 1, + prerequisites: [], + xpReward: 25, + points: 25, + hints: [ + { level: 1, text: 'Use the forward command to move the rover' }, + { level: 2, text: 'The rover draws automatically as it moves' }, + { level: 3, text: 'Try: forward(100)' }, + ], + expectedOutput: { + shapes: [{ shape: 'line', length: 100 }], + tolerance: 5, + }, + orderIndex: 1, + }, + { + id: 'draw-square', + title: 'Draw a Square', + description: 'Draw a perfect square using the rover', + category: 'shapes', + difficulty: 'beginner', + requiredLevel: 1, + prerequisites: ['draw-line'], + xpReward: 50, + points: 50, + hints: [ + { level: 1, text: 'A square has 4 equal sides' }, + { level: 2, text: 'You need to turn 90 degrees between each side' }, + { level: 3, text: 'Use a loop to repeat the pattern' }, + ], + expectedOutput: { + shapes: [{ shape: 'square', width: 100, height: 100 }], + tolerance: 5, + }, + orderIndex: 2, + }, + { + id: 'draw-circle', + title: 'Draw a Circle', + description: 'Draw a circle using the rover', + category: 'shapes', + difficulty: 'intermediate', + requiredLevel: 2, + prerequisites: ['draw-square'], + xpReward: 75, + points: 75, + hints: [ + { level: 1, text: 'A circle is made of many small line segments' }, + { level: 2, text: 'Turn a small amount repeatedly' }, + { level: 3, text: 'Use a loop with small forward and turn commands' }, + ], + expectedOutput: { + shapes: [{ shape: 'circle', radius: 50 }], + tolerance: 10, + }, + orderIndex: 3, + }, +]; + +const MOCK_BADGES = [ + { + id: 'first-steps', + name: 'First Steps', + description: 'Complete your first challenge', + icon: '👣', + }, + { + id: 'shape-starter', + name: 'Shape Starter', + description: 'Complete all beginner challenges', + icon: '🟢', + }, + { + id: 'centurion', + name: 'Centurion', + description: 'Earn 100 XP', + icon: '💪', + }, + { + id: 'master-coder', + name: 'Master Coder', + description: 'Reach Level 5', + icon: '👑', + }, +]; + +// Default export for Next.js App Router +export default ChallengesPage; \ No newline at end of file diff --git a/mission-authoring/src/app/favicon.ico b/mission-authoring/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/mission-authoring/src/app/globals.css b/mission-authoring/src/app/globals.css new file mode 100644 index 0000000..60f6e1d --- /dev/null +++ b/mission-authoring/src/app/globals.css @@ -0,0 +1,336 @@ +@import "tailwindcss"; + +:root, +.light, +[data-theme="light"] { + --radius: 1rem; + + /* Deep space background */ + --background: oklch(0.16 0.04 270); + --foreground: oklch(0.97 0.02 260); + + --card: oklch(0.21 0.05 270); + --card-foreground: oklch(0.97 0.02 260); + --popover: oklch(0.21 0.05 270); + --popover-foreground: oklch(0.97 0.02 260); + + /* NASA mission orange */ + --primary: oklch(0.72 0.19 45); + --primary-foreground: oklch(0.16 0.04 270); + + --secondary: oklch(0.28 0.06 270); + --secondary-foreground: oklch(0.97 0.02 260); + --muted: oklch(0.26 0.04 270); + --muted-foreground: oklch(0.75 0.04 260); + --accent: oklch(0.74 0.18 175); + --accent-foreground: oklch(0.16 0.04 270); + + --destructive: oklch(0.65 0.24 27); + --destructive-foreground: oklch(0.99 0 0); + --border: oklch(0.32 0.05 270); + --input: oklch(0.32 0.05 270); + --ring: oklch(0.72 0.19 45); + + /* Mission tokens */ + --mars: oklch(0.68 0.21 35); + --mars-glow: oklch(0.78 0.18 55); + --buzz: oklch(0.72 0.21 145); /* Buzz Lightyear green */ + --buzz-glow: oklch(0.85 0.18 155); + --cosmic: oklch(0.55 0.22 285); /* deep purple nebula */ + --stardust: oklch(0.92 0.06 95); + + --gradient-mars: linear-gradient(135deg, var(--mars) 0%, var(--mars-glow) 100%); + --gradient-cosmic: linear-gradient(135deg, var(--cosmic) 0%, var(--mars) 100%); + --gradient-launch: linear-gradient(135deg, var(--primary) 0%, oklch(0.65 0.24 12) 100%); + + --shadow-glow-mars: 0 20px 60px -15px color-mix(in oklab, var(--mars) 55%, transparent); + --shadow-glow-buzz: 0 20px 60px -15px color-mix(in oklab, var(--buzz) 55%, transparent); + --shadow-card: 0 30px 80px -30px color-mix(in oklab, var(--cosmic) 60%, transparent); +} + +.dark, +[data-theme="dark"] { + --background: oklch(0.13 0.04 270); + --foreground: oklch(0.97 0.02 260); +} + +/* Force dark theme always */ +html { + color-scheme: dark; +} + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --radius-2xl: calc(var(--radius) + 8px); + --radius-3xl: calc(var(--radius) + 12px); + --radius-4xl: calc(var(--radius) + 16px); + + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-ring-offset-background: var(--background); + + /* Mission palette */ + --color-mars: var(--mars); + --color-mars-glow: var(--mars-glow); + --color-buzz: var(--buzz); + --color-buzz-glow: var(--buzz-glow); + --color-cosmic: var(--cosmic); + --color-stardust: var(--stardust); + + --font-display: "Fredoka", "Baloo 2", system-ui, sans-serif; + --font-mono: "JetBrains Mono", ui-monospace, monospace; + --font-sans: "Inter", system-ui, sans-serif; + + /* Custom Mars Rover Animations */ + --animate-float: float 6s ease-in-out infinite; + --animate-pulse-slow: pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite; + --animate-twinkle: twinkle 2.6s ease-in-out infinite; + --animate-blast: blast-off 1.6s ease-in-out infinite; + --animate-drift: drift 240s linear infinite; + --animate-shoot: shoot 6s ease-in infinite; +} + +@layer base { + * { border-color: var(--color-border); } + + html, body { font-family: var(--font-sans); } + + body { + background-color: var(--color-background); + color: var(--color-foreground); + } + + body::before { + content: ""; + position: fixed; + inset: 0; + z-index: -20; + pointer-events: none; + background-image: + radial-gradient(ellipse 80% 50% at 50% -10%, color-mix(in oklab, var(--cosmic) 40%, transparent), transparent 60%), + radial-gradient(ellipse 60% 40% at 100% 100%, color-mix(in oklab, var(--mars) 35%, transparent), transparent 60%); + } + + h1, h2, h3, .font-display { font-family: var(--font-display); letter-spacing: -0.01em; } +} + +@layer utilities { + .text-gradient-mars { + background: var(--gradient-mars); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + .text-gradient-launch { + background: var(--gradient-launch); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + .bg-gradient-mars { background: var(--gradient-mars); } + .bg-gradient-cosmic { background: var(--gradient-cosmic); } + .shadow-glow-mars { box-shadow: var(--shadow-glow-mars); } + .shadow-glow-buzz { box-shadow: var(--shadow-glow-buzz); } + .shadow-card { box-shadow: var(--shadow-card); } + + .starfield { + background-image: + /* tiny distant stars */ + radial-gradient(1px 1px at 20% 30%, oklch(1 0 0 / 0.95), transparent 60%), + radial-gradient(1px 1px at 70% 60%, oklch(0.95 0.06 95 / 0.9), transparent 60%), + radial-gradient(1px 1px at 85% 20%, oklch(1 0 0 / 0.85), transparent 60%), + radial-gradient(1px 1px at 60% 15%, oklch(1 0 0 / 0.8), transparent 60%), + radial-gradient(1px 1px at 33% 88%, oklch(1 0 0 / 0.8), transparent 60%), + radial-gradient(1px 1px at 92% 78%, oklch(0.9 0.08 220 / 0.85), transparent 60%), + radial-gradient(1px 1px at 5% 50%, oklch(1 0 0 / 0.7), transparent 60%), + radial-gradient(1px 1px at 50% 50%, oklch(1 0 0 / 0.7), transparent 60%), + radial-gradient(1px 1px at 15% 12%, oklch(1 0 0 / 0.75), transparent 60%), + radial-gradient(1px 1px at 78% 42%, oklch(1 0 0 / 0.7), transparent 60%), + /* brighter mid stars */ + radial-gradient(1.5px 1.5px at 10% 70%, oklch(0.95 0.1 60 / 0.95), transparent 55%), + radial-gradient(1.5px 1.5px at 88% 35%, oklch(0.95 0.08 220 / 0.9), transparent 55%), + radial-gradient(1.5px 1.5px at 25% 55%, oklch(1 0 0 / 0.9), transparent 55%), + radial-gradient(1.5px 1.5px at 65% 82%, oklch(0.92 0.1 320 / 0.9), transparent 55%), + /* large glowing stars */ + radial-gradient(2px 2px at 40% 80%, oklch(0.9 0.12 220 / 0.95), transparent 55%), + radial-gradient(2px 2px at 72% 25%, oklch(0.95 0.1 60 / 0.95), transparent 55%), + radial-gradient(2.5px 2.5px at 18% 38%, oklch(0.95 0.14 320 / 0.95), transparent 55%); + background-size: 600px 600px, 600px 600px, 600px 600px, 600px 600px, 600px 600px, 600px 600px, 600px 600px, 600px 600px, 600px 600px, 600px 600px, 900px 900px, 900px 900px, 900px 900px, 900px 900px, 1200px 1200px, 1200px 1200px, 1200px 1200px; + background-repeat: repeat; + } + + .nebula { + background-image: + radial-gradient(ellipse 50% 35% at 22% 28%, color-mix(in oklab, var(--cosmic) 55%, transparent), transparent 70%), + radial-gradient(ellipse 45% 30% at 78% 65%, color-mix(in oklab, var(--mars) 45%, transparent), transparent 70%), + radial-gradient(ellipse 30% 25% at 55% 85%, color-mix(in oklab, oklch(0.6 0.22 320) 50%, transparent), transparent 70%), + radial-gradient(ellipse 25% 20% at 90% 10%, color-mix(in oklab, oklch(0.7 0.18 200) 45%, transparent), transparent 70%); + filter: blur(40px); + will-change: transform; + transform: translateZ(0); + } + + .milky-way { + background: + linear-gradient(115deg, + transparent 30%, + color-mix(in oklab, oklch(0.85 0.08 280) 18%, transparent) 45%, + color-mix(in oklab, oklch(0.9 0.1 320) 22%, transparent) 50%, + color-mix(in oklab, oklch(0.85 0.08 220) 18%, transparent) 55%, + transparent 70%); + filter: blur(20px); + will-change: transform; + transform: translateZ(0); + } + + .shooting-star { + position: absolute; + width: 140px; + height: 1px; + background: linear-gradient(90deg, transparent, oklch(1 0 0 / 0.95), transparent); + border-radius: 9999px; + transform: rotate(-18deg); + opacity: 0; + } +} + +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-12px); } +} + +@keyframes drift { + from { transform: translate3d(0, 0, 0); } + to { transform: translate3d(-1200px, -1200px, 0); } +} + +@keyframes shoot { + 0% { transform: translate(0, 0) rotate(-18deg); opacity: 0; } + 10% { opacity: 1; } + 100% { transform: translate(520px, 170px) rotate(-18deg); opacity: 0; } +} + +@keyframes orbit { + from { transform: rotate(0deg) translateX(140px) rotate(0deg); } + to { transform: rotate(360deg) translateX(140px) rotate(-360deg); } +} + +@keyframes twinkle { + 0%, 100% { opacity: 0.3; } + 50% { opacity: 1; } +} + +@keyframes blast-off { + 0% { transform: translateY(0) scale(1); } + 50% { transform: translateY(-6px) scale(1.02); } + 100% { transform: translateY(0) scale(1); } +} + +@keyframes page-enter { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-float { animation: float 6s ease-in-out infinite; } +.animate-drift { animation: drift 240s linear infinite; } +.animate-shoot { animation: shoot 6s ease-in infinite; } +.animate-twinkle { animation: twinkle 2.6s ease-in-out infinite; } +.animate-blast { animation: blast-off 1.6s ease-in-out infinite; } + +.page-transition-enter { + animation: page-enter 220ms ease-out both; + will-change: transform, opacity; +} + +.workspaceSplitGrid { + display: grid; + gap: 0.5rem; + grid-template-columns: 1fr; + max-height: calc(100vh - 122px); + height: calc(100vh - 122px); + transition: grid-template-columns 0.3s ease-out; +} + +@media (min-width: 1024px) { + .workspaceSplitGrid { + align-items: stretch; + grid-template-columns: + minmax(0, var(--workspace-left, 60fr)) + minmax(320px, var(--workspace-right, 40fr)); + } +} + +/* Blockly Light Theme Overrides */ +.blocklySvg { + background-color: #ffffff !important; +} + +.blocklyMainBackground { + fill: #ffffff !important; +} + +.blocklyToolboxDiv { + background-color: #f8fafc !important; + border-right: 1px solid #e2e8f0 !important; + min-width: 140px !important; + width: min(28vw, 176px) !important; +} + +.blocklyToolboxCategory { + background-color: transparent !important; + color: #1e293b !important; + padding: 5px 7px !important; + font-size: 11px !important; + font-weight: 600 !important; +} + +.blocklyFlyoutBackground { + fill: #f1f5f9 !important; + fill-opacity: 1 !important; +} + +.blocklyTreeRow { + color: #334155 !important; + cursor: pointer; + padding: 1px 6px !important; +} + +.blocklyTreeLabel { + color: #1e293b !important; + font-size: 11px !important; +} + +.blocklyText { + font-size: 11px !important; +} + +.blocklyHtmlInput, +.blocklyDropdownText { + font-size: 11px !important; +} diff --git a/mission-authoring/src/app/history/page.tsx b/mission-authoring/src/app/history/page.tsx new file mode 100644 index 0000000..1453ecc --- /dev/null +++ b/mission-authoring/src/app/history/page.tsx @@ -0,0 +1,23 @@ +import { MissionHistoryScaffold } from '@/components/mission/MissionHistoryScaffold'; + +export default function HistoryPage() { + // TODO: User Story 54 / Task 56 - learner mission status polling and history page. + // TODO: User Story 58 - completed mission follow-up and video notification history. + return ( +
+
+
+

+ Learner Missions +

+

Mission History

+

+ Scaffold route for learner-visible queue and completion history. +

+
+ + +
+
+ ); +} diff --git a/mission-authoring/src/app/layout.tsx b/mission-authoring/src/app/layout.tsx new file mode 100644 index 0000000..9b54f85 --- /dev/null +++ b/mission-authoring/src/app/layout.tsx @@ -0,0 +1,59 @@ +import type { Metadata } from "next"; +import { Inter, Fredoka } from "next/font/google"; +import "./globals.css"; +import { Navbar } from "@/components/layout/Navbar"; +import { LearnerProvider } from "@/contexts/LearnerContext"; +import { PageTransition } from "@/components/layout/PageTransition"; + +const inter = Inter({ + variable: "--font-sans", + subsets: ["latin"], +}); + +const fredoka = Fredoka({ + variable: "--font-display", + subsets: ["latin"], + weight: ["300", "400", "500", "600", "700"], +}); + +export const metadata: Metadata = { + title: "Rover Cadets · Mars Mission Platform", + description: "Pilot Sparky across Mars, code with blocks, and earn mission patches on the Red Planet", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {/* Layered galaxy backdrop */} +
+
+
+
+ + {/* Soft planet glows */} +
+
+ + + + {/* pb on mobile keeps content clear of the fixed bottom tab bar */} +
+ {children} +
+
+ + + ); +} diff --git a/mission-authoring/src/app/leaderboard/page.tsx b/mission-authoring/src/app/leaderboard/page.tsx new file mode 100644 index 0000000..a4ef6bd --- /dev/null +++ b/mission-authoring/src/app/leaderboard/page.tsx @@ -0,0 +1,21 @@ +import { LeaderboardView } from '@/components/challenge/LeaderboardView'; + +export default function LeaderboardPage() { + return ( +
+
+
+
+

Challenge Leaderboard

+

Top Rover Cadets

+

+ Compare progress with other learners, track your rank, and see XP standings for challenge completion. +

+
+ + +
+
+
+ ); +} diff --git a/mission-authoring/src/app/mission/page.tsx b/mission-authoring/src/app/mission/page.tsx new file mode 100644 index 0000000..f52dbbe --- /dev/null +++ b/mission-authoring/src/app/mission/page.tsx @@ -0,0 +1,28 @@ +import { MissionWorkspace } from '@/components/mission/MissionWorkspace'; +import { Suspense } from 'react'; + +export default function MissionPage() { + // TODO: User Story 17 - browser editor experience. + // TODO: User Story 21 - allowlist enforcement before submission. + // TODO: User Story 27 - simulator workflow. + // TODO: User Story 54 - queue confirmation and polling UX. + // TODO: User Story 58 - completion notification UX. + return ( +
+
+
+

+ Learner Workspace +

+

+ Mission Authoring And Simulation +

+
+ + Loading workspace...
}> + + +
+ + ); +} diff --git a/mission-authoring/src/app/missions/[missionId]/MissionVideoClient.tsx b/mission-authoring/src/app/missions/[missionId]/MissionVideoClient.tsx new file mode 100644 index 0000000..0414995 --- /dev/null +++ b/mission-authoring/src/app/missions/[missionId]/MissionVideoClient.tsx @@ -0,0 +1,202 @@ +"use client"; + +import { useEffect, useState } from 'react'; +import { Mission } from '@/core/domain/entities/Mission'; +import Link from 'next/link'; +import { getFirestoreClient } from '@/lib/firebase'; +import { FirestoreMissionRepository } from '@/infrastructure/persistence/FirestoreMissionRepository'; + +function getYouTubeId(url: string | undefined): string | null { + if (!url) return null; + const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; + const match = url.match(regExp); + return (match && match[2].length === 11) ? match[2] : null; +} + +export default function MissionVideoClient({ missionId }: { missionId: string }) { + const [mission, setMission] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + useEffect(() => { + const fetchMission = async () => { + try { + const repository = new FirestoreMissionRepository(getFirestoreClient()); + const loadedMission = await repository.findById(missionId); + + if (loadedMission) { + setMission(loadedMission); + } else { + setError('Mission not found'); + } + } catch (err) { + console.error('Fetch mission error:', err); + setError('Failed to load mission'); + } finally { + setLoading(false); + } + }; + void fetchMission(); + }, [missionId]); + + if (loading) { + return ( +
+
+
+
+
+

Loading mission details...

+
+
+ ); + } + + if (error || !mission) { + return ( +
+
+ ⚠️ +

Error Loading Mission

+

{error || 'Mission not found'}

+ + Return to Catalogue + +
+
+ ); + } + + const youtubeId = getYouTubeId(mission.youtubeUrl || mission.videoUrl); + const missionName = mission.name || `Mission ${mission.id.slice(0, 8)}`; + + return ( +
+
+
+
+ + + + + +

{missionName}

+
+
+ {mission.yardId} + + {new Date(mission.completedAt || mission.submittedAt).toLocaleDateString()} + {mission.challengeId && ( + <> + + {mission.challengeId} + + )} +
+
+
+ +
+
+
+
+ {youtubeId ? ( +
+