From 6aeff37eb886a3a767e0b8cbc097074bda515f93 Mon Sep 17 00:00:00 2001 From: Halit Aksoy Date: Fri, 16 Oct 2026 11:47:40 +0300 Subject: [PATCH] feat(backend): replace the Node backend with the Quarkus backend The Quarkus project moves to apps/backend and the Express sources go. The image is built from the repo root as before, with Temurin 25, and runs as a non-root user on port 6000. Compose passes the database as a JDBC URL with the same MANCALA_DB_PASSWORD. --- .dockerignore | 1 + apps/backend-quarkus/.gitignore | 9 - apps/backend/.gitignore | 43 +- .../.mvn/wrapper/maven-wrapper.properties | 0 apps/backend/.vscode/launch.json | 25 - apps/backend/Dockerfile | 41 +- apps/backend/README.md | 82 - apps/backend/build_and_deploy.sh | 4 - apps/backend/jest.config.js | 6 - apps/{backend-quarkus => backend}/mvnw | 0 apps/{backend-quarkus => backend}/mvnw.cmd | 0 apps/backend/package.json | 44 - apps/{backend-quarkus => backend}/pom.xml | 0 .../scripts/record-engine-games.js | 0 apps/backend/src/auth/AuthConfig.ts | 16 - apps/backend/src/auth/AuthUser.ts | 1 - .../backend/src/auth/KeycloakTokenVerifier.ts | 72 - apps/backend/src/auth/TokenVerifier.ts | 13 - apps/backend/src/auth/index.ts | 4 - apps/backend/src/cache/Cache.ts | 40 - apps/backend/src/consts/config.ts | 9 - apps/backend/src/db/migrations/001_games.ts | 29 - apps/backend/src/db/migrations/002_clock.ts | 4 - apps/backend/src/db/migrations/index.ts | 13 - apps/backend/src/db/postgres.ts | 41 - apps/backend/src/game/GameClock.ts | 33 - apps/backend/src/game/GameClockScheduler.ts | 25 - apps/backend/src/game/GameCrashManager.ts | 38 - apps/backend/src/game/GameManager.ts | 402 -- apps/backend/src/game/GameRecorder.ts | 82 - apps/backend/src/game/GameSerialization.ts | 139 - apps/backend/src/game/LiveGames.ts | 89 - apps/backend/src/game/gamestore/GameStore.ts | 19 - .../src/game/gamestore/GameStoreImpl.ts | 50 - apps/backend/src/game/publicIds.ts | 11 - .../src/history/GameHistoryRepository.ts | 115 - .../backend/src/history/GameHistoryService.ts | 65 - apps/backend/src/index.ts | 86 - apps/backend/src/live/LiveGameStore.ts | 61 - apps/backend/src/live/valkey.ts | 33 - .../main/java/app/playmancala/Backend.java | 0 .../auth/InvalidTokenException.java | 0 .../auth/KeycloakTokenVerifier.java | 0 .../app/playmancala/auth/TokenVerifier.java | 0 .../auth/TokenVerifierProducer.java | 0 .../java/app/playmancala/engine/Bank.java | 0 .../java/app/playmancala/engine/Board.java | 0 .../app/playmancala/engine/CommonBoard.java | 0 .../playmancala/engine/CommonMancalaGame.java | 0 .../java/app/playmancala/engine/GameRule.java | 0 .../app/playmancala/engine/GameState.java | 0 .../java/app/playmancala/engine/GameStep.java | 0 .../app/playmancala/engine/MancalaGame.java | 0 .../playmancala/engine/MoveHistoryItem.java | 0 .../main/java/app/playmancala/engine/Pit.java | 0 .../java/app/playmancala/engine/PitType.java | 0 .../engine/rules/GRClearBoardAtEnd.java | 0 .../engine/rules/GRDoubleStoneInPit.java | 0 .../engine/rules/GRLastStoneInBank.java | 0 .../engine/rules/GRLastStoneInEmptyPit.java | 0 .../app/playmancala/game/BackendGame.java | 0 .../java/app/playmancala/game/CrashLog.java | 0 .../playmancala/game/GameClockScheduler.java | 0 .../java/app/playmancala/game/GameClocks.java | 0 .../app/playmancala/game/GameManager.java | 0 .../app/playmancala/game/GameRecorder.java | 0 .../playmancala/game/GameSerialization.java | 0 .../java/app/playmancala/game/GameStore.java | 0 .../java/app/playmancala/game/LiveGames.java | 0 .../java/app/playmancala/game/MatchMaker.java | 0 .../java/app/playmancala/game/PublicIds.java | 0 .../history/GameHistoryRepository.java | 0 .../history/GameHistoryService.java | 0 .../app/playmancala/history/Migrations.java | 0 .../PostgresGameHistoryRepository.java | 0 .../java/app/playmancala/http/BearerAuth.java | 0 .../app/playmancala/http/ErrorMapper.java | 0 .../app/playmancala/http/GameResource.java | 0 .../main/java/app/playmancala/live/Cache.java | 0 .../app/playmancala/live/LiveGameStore.java | 0 .../app/playmancala/live/ValkeyCache.java | 0 .../playmancala/live/ValkeyLiveGameStore.java | 0 .../app/playmancala/loop/Cancellable.java | 0 .../java/app/playmancala/loop/GameLoop.java | 0 .../java/app/playmancala/loop/Scheduler.java | 0 .../java/app/playmancala/model/AuthUser.java | 0 .../java/app/playmancala/model/Channels.java | 0 .../app/playmancala/model/ClientGame.java | 0 .../java/app/playmancala/model/EndReason.java | 0 .../model/FinishedGameSummary.java | 0 .../java/app/playmancala/model/GameClock.java | 0 .../app/playmancala/model/GameEndMessage.java | 0 .../app/playmancala/model/GameRecord.java | 0 .../app/playmancala/model/GameResult.java | 0 .../model/GameUsersConnectionInfo.java | 0 .../main/java/app/playmancala/model/Json.java | 0 .../playmancala/model/LiveGameSummary.java | 0 .../app/playmancala/model/MoveRecord.java | 0 .../app/playmancala/model/PlayerSummary.java | 0 .../main/java/app/playmancala/model/Seat.java | 0 .../playmancala/model/UserConnectionInfo.java | 0 .../main/java/app/playmancala/rtmt/Rtmt.java | 0 .../app/playmancala/rtmt/RtmtEndpoint.java | 0 .../java/app/playmancala/rtmt/RtmtServer.java | 0 .../java/app/playmancala/rtmt/RtmtSocket.java | 0 .../src/main/resources/application.properties | 0 .../resources/db/migrations/001_games.sql | 0 .../resources/db/migrations/002_clock.sql | 0 apps/backend/src/matchmaker/MatchMaker.ts | 7 - apps/backend/src/matchmaker/MatchMakerImpl.ts | 45 - apps/backend/src/models/Game.ts | 24 - .../backend/src/rtmt/encode_decode_message.ts | 15 - apps/backend/src/rtmt/rtmt.ts | 13 - apps/backend/src/rtmt/rtmt_websocket.ts | 239 - apps/backend/src/server/ExpressApp.ts | 113 - apps/backend/src/server/WebServer.ts | 21 - apps/backend/src/server/authMiddleware.ts | 21 - .../auth/KeycloakTokenVerifierTest.java | 0 .../playmancala/engine/EngineParityTest.java | 0 .../playmancala/engine/MancalaGameTest.java | 0 .../java/app/playmancala/fakes/FakeRtmt.java | 0 .../app/playmancala/fakes/InMemoryCache.java | 0 .../fakes/InMemoryGameHistoryRepository.java | 0 .../fakes/InMemoryLiveGameStore.java | 0 .../game/GameClockSchedulerTest.java | 0 .../app/playmancala/game/GameManagerTest.java | 0 .../app/playmancala/game/LiveGamesTest.java | 0 .../history/GameHistoryServiceTest.java | 0 .../PostgresGameHistoryRepositoryTest.java | 0 .../java/app/playmancala/http/ApiTest.java | 0 .../app/playmancala/live/ValkeyStoreTest.java | 0 .../app/playmancala/loop/ManualScheduler.java | 0 .../playmancala/loop/ManualSchedulerTest.java | 0 .../app/playmancala/rtmt/RtmtServerTest.java | 0 .../support/FakeTokenVerifier.java | 0 .../app/playmancala/support/TestClient.java | 0 .../test/resources/engine/recorded-games.json | 0 apps/backend/src/util/key_factory.ts | 6 - .../tests/auth/KeycloakTokenVerifier.test.ts | 61 - apps/backend/tests/game/FakeRtmt.ts | 53 - .../tests/game/GameClockScheduler.test.ts | 22 - apps/backend/tests/game/GameManager.test.ts | 462 -- apps/backend/tests/game/LiveGames.test.ts | 92 - apps/backend/tests/game/fakes.ts | 82 - .../PostgresGameHistoryRepository.test.ts | 127 - apps/backend/tests/live/ValkeyStore.test.ts | 113 - apps/backend/tests/mancala.test.ts | 14 - .../backend/tests/rtmt/rtmt_websocket.test.ts | 209 - apps/backend/tests/server/ExpressApp.test.ts | 210 - apps/backend/tests/support/NodeEnvironment.js | 19 - apps/backend/tests/support/containers.ts | 31 - apps/backend/tsconfig.json | 16 - apps/backend/yarn.lock | 4805 ----------------- apps/compose.yaml | 4 +- docs/specs/quarkus-backend/tasks.md | 2 +- 155 files changed, 33 insertions(+), 8538 deletions(-) delete mode 100644 apps/backend-quarkus/.gitignore rename apps/{backend-quarkus => backend}/.mvn/wrapper/maven-wrapper.properties (100%) delete mode 100644 apps/backend/.vscode/launch.json delete mode 100644 apps/backend/README.md delete mode 100644 apps/backend/build_and_deploy.sh delete mode 100644 apps/backend/jest.config.js rename apps/{backend-quarkus => backend}/mvnw (100%) rename apps/{backend-quarkus => backend}/mvnw.cmd (100%) delete mode 100644 apps/backend/package.json rename apps/{backend-quarkus => backend}/pom.xml (100%) rename apps/{backend-quarkus => backend}/scripts/record-engine-games.js (100%) delete mode 100644 apps/backend/src/auth/AuthConfig.ts delete mode 100644 apps/backend/src/auth/AuthUser.ts delete mode 100644 apps/backend/src/auth/KeycloakTokenVerifier.ts delete mode 100644 apps/backend/src/auth/TokenVerifier.ts delete mode 100644 apps/backend/src/auth/index.ts delete mode 100644 apps/backend/src/cache/Cache.ts delete mode 100644 apps/backend/src/consts/config.ts delete mode 100644 apps/backend/src/db/migrations/001_games.ts delete mode 100644 apps/backend/src/db/migrations/002_clock.ts delete mode 100644 apps/backend/src/db/migrations/index.ts delete mode 100644 apps/backend/src/db/postgres.ts delete mode 100644 apps/backend/src/game/GameClock.ts delete mode 100644 apps/backend/src/game/GameClockScheduler.ts delete mode 100644 apps/backend/src/game/GameCrashManager.ts delete mode 100644 apps/backend/src/game/GameManager.ts delete mode 100644 apps/backend/src/game/GameRecorder.ts delete mode 100644 apps/backend/src/game/GameSerialization.ts delete mode 100644 apps/backend/src/game/LiveGames.ts delete mode 100644 apps/backend/src/game/gamestore/GameStore.ts delete mode 100644 apps/backend/src/game/gamestore/GameStoreImpl.ts delete mode 100644 apps/backend/src/game/publicIds.ts delete mode 100644 apps/backend/src/history/GameHistoryRepository.ts delete mode 100644 apps/backend/src/history/GameHistoryService.ts delete mode 100644 apps/backend/src/index.ts delete mode 100644 apps/backend/src/live/LiveGameStore.ts delete mode 100644 apps/backend/src/live/valkey.ts rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/Backend.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/auth/InvalidTokenException.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/auth/KeycloakTokenVerifier.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/auth/TokenVerifier.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/auth/TokenVerifierProducer.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/Bank.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/Board.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/CommonBoard.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/CommonMancalaGame.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/GameRule.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/GameState.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/GameStep.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/MancalaGame.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/MoveHistoryItem.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/Pit.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/PitType.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/rules/GRClearBoardAtEnd.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/rules/GRDoubleStoneInPit.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/rules/GRLastStoneInBank.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/engine/rules/GRLastStoneInEmptyPit.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/BackendGame.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/CrashLog.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/GameClockScheduler.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/GameClocks.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/GameManager.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/GameRecorder.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/GameSerialization.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/GameStore.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/LiveGames.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/MatchMaker.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/game/PublicIds.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/history/GameHistoryRepository.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/history/GameHistoryService.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/history/Migrations.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/history/PostgresGameHistoryRepository.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/http/BearerAuth.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/http/ErrorMapper.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/http/GameResource.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/live/Cache.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/live/LiveGameStore.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/live/ValkeyCache.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/live/ValkeyLiveGameStore.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/loop/Cancellable.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/loop/GameLoop.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/loop/Scheduler.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/AuthUser.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/Channels.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/ClientGame.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/EndReason.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/FinishedGameSummary.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/GameClock.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/GameEndMessage.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/GameRecord.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/GameResult.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/GameUsersConnectionInfo.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/Json.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/LiveGameSummary.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/MoveRecord.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/PlayerSummary.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/Seat.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/model/UserConnectionInfo.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/rtmt/Rtmt.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/rtmt/RtmtEndpoint.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/rtmt/RtmtServer.java (100%) rename apps/{backend-quarkus => backend}/src/main/java/app/playmancala/rtmt/RtmtSocket.java (100%) rename apps/{backend-quarkus => backend}/src/main/resources/application.properties (100%) rename apps/{backend-quarkus => backend}/src/main/resources/db/migrations/001_games.sql (100%) rename apps/{backend-quarkus => backend}/src/main/resources/db/migrations/002_clock.sql (100%) delete mode 100644 apps/backend/src/matchmaker/MatchMaker.ts delete mode 100644 apps/backend/src/matchmaker/MatchMakerImpl.ts delete mode 100644 apps/backend/src/models/Game.ts delete mode 100644 apps/backend/src/rtmt/encode_decode_message.ts delete mode 100644 apps/backend/src/rtmt/rtmt.ts delete mode 100644 apps/backend/src/rtmt/rtmt_websocket.ts delete mode 100644 apps/backend/src/server/ExpressApp.ts delete mode 100644 apps/backend/src/server/WebServer.ts delete mode 100644 apps/backend/src/server/authMiddleware.ts rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/auth/KeycloakTokenVerifierTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/engine/EngineParityTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/engine/MancalaGameTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/fakes/FakeRtmt.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/fakes/InMemoryCache.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/fakes/InMemoryGameHistoryRepository.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/fakes/InMemoryLiveGameStore.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/game/GameClockSchedulerTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/game/GameManagerTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/game/LiveGamesTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/history/GameHistoryServiceTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/history/PostgresGameHistoryRepositoryTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/http/ApiTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/live/ValkeyStoreTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/loop/ManualScheduler.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/loop/ManualSchedulerTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/rtmt/RtmtServerTest.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/support/FakeTokenVerifier.java (100%) rename apps/{backend-quarkus => backend}/src/test/java/app/playmancala/support/TestClient.java (100%) rename apps/{backend-quarkus => backend}/src/test/resources/engine/recorded-games.json (100%) delete mode 100644 apps/backend/src/util/key_factory.ts delete mode 100644 apps/backend/tests/auth/KeycloakTokenVerifier.test.ts delete mode 100644 apps/backend/tests/game/FakeRtmt.ts delete mode 100644 apps/backend/tests/game/GameClockScheduler.test.ts delete mode 100644 apps/backend/tests/game/GameManager.test.ts delete mode 100644 apps/backend/tests/game/LiveGames.test.ts delete mode 100644 apps/backend/tests/game/fakes.ts delete mode 100644 apps/backend/tests/history/PostgresGameHistoryRepository.test.ts delete mode 100644 apps/backend/tests/live/ValkeyStore.test.ts delete mode 100644 apps/backend/tests/mancala.test.ts delete mode 100644 apps/backend/tests/rtmt/rtmt_websocket.test.ts delete mode 100644 apps/backend/tests/server/ExpressApp.test.ts delete mode 100644 apps/backend/tests/support/NodeEnvironment.js delete mode 100644 apps/backend/tests/support/containers.ts delete mode 100644 apps/backend/tsconfig.json delete mode 100644 apps/backend/yarn.lock diff --git a/.dockerignore b/.dockerignore index b49f95b..de82437 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ # Images for apps/backend and apps/frontend are built with the repo root as context. **/node_modules **/dist +**/target **/.parcel-cache .git apps/mobile diff --git a/apps/backend-quarkus/.gitignore b/apps/backend-quarkus/.gitignore deleted file mode 100644 index f7e7fea..0000000 --- a/apps/backend-quarkus/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -target/ -.idea -*.iml -.vscode -.factorypath - -# Written at runtime -access*.log -crashes/ diff --git a/apps/backend/.gitignore b/apps/backend/.gitignore index 9750e23..f7e7fea 100644 --- a/apps/backend/.gitignore +++ b/apps/backend/.gitignore @@ -1,40 +1,9 @@ -lib-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz -*.swp - -pids -logs -results -tmp - -# Build -public/css/main.css - -# Coverage reports -coverage - -# API keys and secrets -.env - -# Dependency directory -node_modules -bower_components - -# Editors +target/ .idea *.iml +.vscode +.factorypath -# OS metadata -.DS_Store -Thumbs.db - -# Ignore built ts files -dist/**/* - -crashes \ No newline at end of file +# Written at runtime +access*.log +crashes/ diff --git a/apps/backend-quarkus/.mvn/wrapper/maven-wrapper.properties b/apps/backend/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from apps/backend-quarkus/.mvn/wrapper/maven-wrapper.properties rename to apps/backend/.mvn/wrapper/maven-wrapper.properties diff --git a/apps/backend/.vscode/launch.json b/apps/backend/.vscode/launch.json deleted file mode 100644 index 8a9f849..0000000 --- a/apps/backend/.vscode/launch.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceFolder}/src/index.ts", - "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Jest Current File", - "program": "${workspaceFolder}/node_modules/.bin/jest", - "args": ["${relativeFile}"], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "windows": { - "program": "${workspaceFolder}/node_modules/jest/bin/jest" - } - } - ] - } \ No newline at end of file diff --git a/apps/backend/Dockerfile b/apps/backend/Dockerfile index 6d115d2..02eef4c 100644 --- a/apps/backend/Dockerfile +++ b/apps/backend/Dockerfile @@ -1,26 +1,29 @@ # Build from the repo root: docker build -f apps/backend/Dockerfile . -FROM node:lts-buster-slim +FROM eclipse-temurin:25.0.4_7-jdk-noble AS build + +# The Maven wrapper checks the .zip it downloads; without unzip it would fetch a .tar.gz that fails the check. +RUN apt-get update && apt-get install -y --no-install-recommends unzip && rm -rf /var/lib/apt/lists/* + +WORKDIR /build + +# Dependencies first, so they are cached until the pom changes +COPY apps/backend/.mvn .mvn +COPY apps/backend/mvnw apps/backend/pom.xml ./ +RUN ./mvnw -B -q quarkus:go-offline + +COPY apps/backend/src src +RUN ./mvnw -B -q package -DskipTests + +FROM eclipse-temurin:25.0.4_7-jre-noble ENV PORT=6000 -WORKDIR /usr/src/app - -# Local packages the backend links to -COPY package.json package.json -COPY packages packages -RUN yarn build:packages - -WORKDIR /usr/src/app/apps/backend - -COPY apps/backend/package.json package.json -COPY apps/backend/yarn.lock yarn.lock - -RUN yarn - -COPY apps/backend . - -RUN yarn build +# The app writes access.log and crashes/ to its working directory. +RUN useradd --system --create-home mancala && install -d -o mancala /home/mancala/app +WORKDIR /home/mancala/app +COPY --from=build --chown=mancala /build/target/quarkus-app/ ./ +USER mancala EXPOSE ${PORT} -CMD [ "yarn", "start"] +CMD ["java", "-jar", "quarkus-run.jar"] diff --git a/apps/backend/README.md b/apps/backend/README.md deleted file mode 100644 index 9950c19..0000000 --- a/apps/backend/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Mancala Backend - -This is the backend code for the Mancala project, written in Express.js. - -## Getting Started - -### Install Packages - -```bash -yarn install -``` - -### Start in Development Mode - -```bash -yarn dev -``` - -### Start in Release Mode - -```bash -yarn build -yarn start -``` - -### Development Dependencies - -This package links the local packages `@mancala/core` and `mancala.js`. Build them first with `yarn build:packages` in the repo root. - -## Authentication - -Clients log in with Keycloak (see `apps/keycloak`), either as a guest or with an account, and send the access token -over the WebSocket right after connecting: - -```json -{ "channel": "auth", "message": { "token": "" } } -``` - -The server answers on `on_auth` with the user (`{ id, name, isGuest }`) or on `on_auth_error` and closes the socket -with code 4001. Other messages are ignored until the socket is authenticated, and connections that do not -authenticate within 5 seconds are closed. Send `auth` again with the new token after refreshing it. -User ids of Keycloak users are the token subject prefixed with `kc:`. - -| Variable | Default | Purpose | -|---|---|---| -| `KEYCLOAK_ISSUER` | `http://localhost:8080/realms/mancala` | Token issuer; must match the `iss` claim exactly | -| `KEYCLOAK_JWKS_URL` | issuer + `/protocol/openid-connect/certs` | Where to fetch signing keys, if the backend reaches Keycloak at a different address | -| `KEYCLOAK_CLIENT_IDS` | `mancala-web` | Comma-separated clients whose tokens are accepted | -| `LEGACY_USERKEY_AUTH` | `true` | Also accept old clients that connect with `?userKey=` and the `/register` endpoint. Turn off once no released client uses it, because a userKey is not a secret the server can check | - -## Saved games and live games - -Finished games are saved in Postgres; live games are copied to Valkey after every change and restored when the -backend starts, so a restart or deploy does not end them. The design is in `docs/specs/game-history`. - -| Variable | Default | Purpose | -|---|---|---| -| `DATABASE_URL` | `postgres://mancala:mancala@localhost:5433/mancala` | Saved games; migrations run on startup | -| `VALKEY_URL` | `redis://localhost:6379` | Live game state and short-lived caches | - -The backend keeps running when either is down: games are still played, finished games are saved once Postgres is -back, and lists are served without the missing data. - -## Game clock - -Each player has a clock for the whole game; only the clock of the player whose turn it is runs, also while they are -offline. A move that arrives after the mover's time has run out is refused, and `GameClockScheduler` ends games whose -running clock reaches zero once a second, so the opponent wins on time even if nobody is connected. Time while the -backend is down is not charged. The design is in `docs/specs/game-clock`. - -| Variable | Default | Purpose | -|---|---|---| -| `GAME_CLOCK_MS` | `300000` | Time each player has for a game; a short value such as `40000` helps when testing by hand | - -| Route | Auth | Response | -|---|---|---| -| `GET /game/:id` | – | A live game, or `{ status: "finished", record }` | -| `GET /games/popular?limit=5` | – | `{ kind: "live" \| "finished", games }` | -| `GET /games/me/active` | Bearer token | `{ game }` or `{ game: null }` | -| `GET /games/me?limit=3` | Bearer token | `{ games }`, newest first | - -Tests use Docker (testcontainers) for Postgres and Valkey. diff --git a/apps/backend/build_and_deploy.sh b/apps/backend/build_and_deploy.sh deleted file mode 100644 index 8db7f0b..0000000 --- a/apps/backend/build_and_deploy.sh +++ /dev/null @@ -1,4 +0,0 @@ -rm -rf node_modules -yarn -yarn build -yarn start \ No newline at end of file diff --git a/apps/backend/jest.config.js b/apps/backend/jest.config.js deleted file mode 100644 index 10139cf..0000000 --- a/apps/backend/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - transform: {'^.+\\.ts?$': 'ts-jest'}, - testEnvironment: '/tests/support/NodeEnvironment.js', - testRegex: '/tests/.*\\.(test|spec)?\\.(ts|tsx)$', - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - }; diff --git a/apps/backend-quarkus/mvnw b/apps/backend/mvnw similarity index 100% rename from apps/backend-quarkus/mvnw rename to apps/backend/mvnw diff --git a/apps/backend-quarkus/mvnw.cmd b/apps/backend/mvnw.cmd similarity index 100% rename from apps/backend-quarkus/mvnw.cmd rename to apps/backend/mvnw.cmd diff --git a/apps/backend/package.json b/apps/backend/package.json deleted file mode 100644 index 2b08a10..0000000 --- a/apps/backend/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@mancala/backend", - "version": "0.4.7", - "description": "", - "main": "index.js", - "scripts": { - "start": "node dist/index.js", - "dev": "nodemon src/index.ts", - "build": "tsc", - "test": "jest", - "coverage": "jest --coverage" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@mancala/core": "link:../../packages/core", - "@types/cors": "^2.8.10", - "@types/morgan": "^1.9.3", - "@types/uuid": "^8.3.0", - "@types/ws": "^7.4.5", - "cors": "^2.8.5", - "express": "^4.17.1", - "iovalkey": "0.4.0", - "jose": "5.10.0", - "mancala.js": "link:../../packages/mancala.js", - "morgan": "^1.10.0", - "pg": "8.23.1", - "uuid": "^8.3.2", - "ws": "^7.5.0" - }, - "devDependencies": { - "@types/express": "^4.17.12", - "@types/jest": "^26.0.23", - "@types/node": "^15.12.4", - "@types/pg": "8.23.1", - "jest": "^27.0.5", - "nodemon": "^2.0.7", - "testcontainers": "12.2.0", - "ts-jest": "^27.0.3", - "ts-node": "^10.0.0", - "typescript": "^4.7.4" - } -} diff --git a/apps/backend-quarkus/pom.xml b/apps/backend/pom.xml similarity index 100% rename from apps/backend-quarkus/pom.xml rename to apps/backend/pom.xml diff --git a/apps/backend-quarkus/scripts/record-engine-games.js b/apps/backend/scripts/record-engine-games.js similarity index 100% rename from apps/backend-quarkus/scripts/record-engine-games.js rename to apps/backend/scripts/record-engine-games.js diff --git a/apps/backend/src/auth/AuthConfig.ts b/apps/backend/src/auth/AuthConfig.ts deleted file mode 100644 index 3a543c2..0000000 --- a/apps/backend/src/auth/AuthConfig.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface AuthConfig { - keycloakIssuer: string; - keycloakJwksUrl?: string; - keycloakClientIds: string[]; - // Accept old clients that identify themselves with ?userKey= instead of a token. - legacyUserKeyAuth: boolean; -} - -export function readAuthConfig(env: NodeJS.ProcessEnv = process.env): AuthConfig { - return { - keycloakIssuer: env.KEYCLOAK_ISSUER || "http://localhost:8080/realms/mancala", - keycloakJwksUrl: env.KEYCLOAK_JWKS_URL || undefined, - keycloakClientIds: (env.KEYCLOAK_CLIENT_IDS || "mancala-web").split(",").map((id) => id.trim()).filter(Boolean), - legacyUserKeyAuth: (env.LEGACY_USERKEY_AUTH ?? "true") === "true", - }; -} diff --git a/apps/backend/src/auth/AuthUser.ts b/apps/backend/src/auth/AuthUser.ts deleted file mode 100644 index 95adb3f..0000000 --- a/apps/backend/src/auth/AuthUser.ts +++ /dev/null @@ -1 +0,0 @@ -export type { AuthUser } from "@mancala/core"; diff --git a/apps/backend/src/auth/KeycloakTokenVerifier.ts b/apps/backend/src/auth/KeycloakTokenVerifier.ts deleted file mode 100644 index 43f918c..0000000 --- a/apps/backend/src/auth/KeycloakTokenVerifier.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { createRemoteJWKSet, errors, JWTPayload, JWTVerifyGetKey, jwtVerify } from "jose"; -import { AuthUser } from "./AuthUser"; -import { InvalidTokenError, TokenVerifier } from "./TokenVerifier"; - -export const KEYCLOAK_USER_ID_PREFIX = "kc:"; -export const GUEST_ROLE = "guest"; - -export interface KeycloakTokenVerifierOptions { - // Realm issuer, e.g. https://auth.playmancala.app/realms/mancala - issuer: string; - // Clients whose access tokens are accepted (checked against the azp claim). - allowedClientIds: string[]; - // Where to fetch signing keys from. Defaults to the issuer's certs endpoint. - jwksUrl?: string; - // Overrides key lookup, for tests. - keys?: JWTVerifyGetKey; -} - -type KeycloakAccessToken = JWTPayload & { - typ?: string; - azp?: string; - preferred_username?: string; - realm_access?: { roles?: string[] }; -}; - -/** - * Verifies Keycloak access tokens offline against the realm's published signing keys. - * Player ids are the token subject prefixed with "kc:", so they can never collide with - * ids that legacy userKey clients choose for themselves. - */ -export class KeycloakTokenVerifier implements TokenVerifier { - private readonly issuer: string; - private readonly allowedClientIds: string[]; - private readonly keys: JWTVerifyGetKey; - - constructor(options: KeycloakTokenVerifierOptions) { - this.issuer = options.issuer; - this.allowedClientIds = options.allowedClientIds; - this.keys = options.keys ?? - createRemoteJWKSet(new URL(options.jwksUrl ?? `${options.issuer}/protocol/openid-connect/certs`)); - } - - async verify(token: string): Promise { - let payload: KeycloakAccessToken; - try { - ({ payload } = await jwtVerify(token, this.keys, { - issuer: this.issuer, - algorithms: ["RS256"], - requiredClaims: ["sub", "exp"], - })); - } catch (err) { - if (err instanceof errors.JOSEError) { - throw new InvalidTokenError(err.code); - } - throw err; - } - - // Keycloak ID tokens and refresh tokens are signed by the same keys, only accept access tokens. - if (payload.typ !== "Bearer") { - throw new InvalidTokenError("not an access token"); - } - if (!payload.azp || !this.allowedClientIds.includes(payload.azp)) { - throw new InvalidTokenError("token issued to an unknown client"); - } - - return { - id: KEYCLOAK_USER_ID_PREFIX + payload.sub, - name: payload.preferred_username, - isGuest: payload.realm_access?.roles?.includes(GUEST_ROLE) ?? false, - }; - } -} diff --git a/apps/backend/src/auth/TokenVerifier.ts b/apps/backend/src/auth/TokenVerifier.ts deleted file mode 100644 index 9406a46..0000000 --- a/apps/backend/src/auth/TokenVerifier.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AuthUser } from "./AuthUser"; - -export class InvalidTokenError extends Error { - constructor(message: string) { - super(message); - this.name = "InvalidTokenError"; - } -} - -export interface TokenVerifier { - // Resolves to the token's user, or rejects with InvalidTokenError. - verify(token: string): Promise; -} diff --git a/apps/backend/src/auth/index.ts b/apps/backend/src/auth/index.ts deleted file mode 100644 index 43eeddb..0000000 --- a/apps/backend/src/auth/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./AuthUser"; -export * from "./AuthConfig"; -export * from "./TokenVerifier"; -export * from "./KeycloakTokenVerifier"; diff --git a/apps/backend/src/cache/Cache.ts b/apps/backend/src/cache/Cache.ts deleted file mode 100644 index eb2e3d5..0000000 --- a/apps/backend/src/cache/Cache.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Valkey } from "iovalkey"; - -const PREFIX = "mancala:cache:"; - -// Short-lived cache of JSON values. Never fails: errors count as a miss, so callers just compute the value. -export interface Cache { - getJson(key: string): Promise; - setJson(key: string, value: unknown, ttlSeconds: number): Promise; - delete(...keys: string[]): Promise; -} - -export class ValkeyCache implements Cache { - constructor(private readonly client: Valkey) { } - - async getJson(key: string): Promise { - try { - const value = await this.client.get(PREFIX + key); - return value === null ? undefined : JSON.parse(value) as T; - } catch (err) { - return undefined; - } - } - - async setJson(key: string, value: unknown, ttlSeconds: number): Promise { - try { - await this.client.set(PREFIX + key, JSON.stringify(value), "EX", ttlSeconds); - } catch (err) { - // A missing cache entry only costs a database query. - } - } - - async delete(...keys: string[]): Promise { - if (keys.length === 0) return; - try { - await this.client.del(...keys.map((key) => PREFIX + key)); - } catch (err) { - // Entries expire on their own. - } - } -} diff --git a/apps/backend/src/consts/config.ts b/apps/backend/src/consts/config.ts deleted file mode 100644 index 909455f..0000000 --- a/apps/backend/src/consts/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const RTMT_WS_PING_INTERVAL = 2000; - -// Time each player has for a whole game (docs/specs/game-clock). -export const GAME_CLOCK_MS = positiveInteger(process.env.GAME_CLOCK_MS) ?? 5 * 60 * 1000; - -function positiveInteger(value: string | undefined): number | undefined { - const number = Number(value); - return value && Number.isInteger(number) && number > 0 ? number : undefined; -} diff --git a/apps/backend/src/db/migrations/001_games.ts b/apps/backend/src/db/migrations/001_games.ts deleted file mode 100644 index 837068b..0000000 --- a/apps/backend/src/db/migrations/001_games.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const sql = ` -CREATE TABLE games ( - id uuid PRIMARY KEY, - player1_id text NOT NULL, - player2_id text NOT NULL, - result text CHECK (result IN ('player1', 'player2', 'draw')), - end_reason text NOT NULL CHECK (end_reason IN ('completed', 'left', 'timeout', 'inactive')), - left_user_id text, - started_at timestamptz NOT NULL, - ended_at timestamptz NOT NULL, - move_count integer NOT NULL, - final_board jsonb NOT NULL, - moves jsonb NOT NULL, - peak_spectators integer NOT NULL DEFAULT 0, - total_spectators integer NOT NULL DEFAULT 0 -); - -CREATE TABLE game_players ( - game_id uuid NOT NULL REFERENCES games (id) ON DELETE CASCADE, - seat smallint NOT NULL CHECK (seat IN (1, 2)), - user_id text NOT NULL, - name text, - is_guest boolean NOT NULL, - PRIMARY KEY (game_id, seat) -); - -CREATE INDEX game_players_user_id ON game_players (user_id); -CREATE INDEX games_popular ON games (peak_spectators DESC, ended_at DESC); -`; diff --git a/apps/backend/src/db/migrations/002_clock.ts b/apps/backend/src/db/migrations/002_clock.ts deleted file mode 100644 index 461ecff..0000000 --- a/apps/backend/src/db/migrations/002_clock.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Time each player started with; NULL for games saved before the game clock. Each move's clock is in `moves`. -export const sql = ` -ALTER TABLE games ADD COLUMN clock_initial_ms integer; -`; diff --git a/apps/backend/src/db/migrations/index.ts b/apps/backend/src/db/migrations/index.ts deleted file mode 100644 index ce0b17f..0000000 --- a/apps/backend/src/db/migrations/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { sql as games001 } from "./001_games"; -import { sql as clock002 } from "./002_clock"; - -export interface Migration { - version: string; - sql: string; -} - -// Applied in this order; never change a migration that has been released, add a new one instead. -export const migrations: Migration[] = [ - { version: "001_games", sql: games001 }, - { version: "002_clock", sql: clock002 }, -]; diff --git a/apps/backend/src/db/postgres.ts b/apps/backend/src/db/postgres.ts deleted file mode 100644 index 5ab05c3..0000000 --- a/apps/backend/src/db/postgres.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Pool } from "pg"; -import { Migration, migrations as allMigrations } from "./migrations"; - -const MIGRATION_LOCK_ID = 727274; - -export function createPool(databaseUrl: string): Pool { - const pool = new Pool({ connectionString: databaseUrl, max: 5, connectionTimeoutMillis: 5000 }); - // Without a listener, an error on an idle connection (e.g. Postgres restarting) would crash the process. - pool.on("error", (err) => console.error("(DB) Idle connection error", err.message)); - return pool; -} - -// Applies the migrations that have not run yet, each in its own transaction. -export async function runMigrations(pool: Pool, migrations: Migration[] = allMigrations): Promise { - const client = await pool.connect(); - try { - await client.query("SELECT pg_advisory_lock($1)", [MIGRATION_LOCK_ID]); - await client.query(`CREATE TABLE IF NOT EXISTS schema_migrations ( - version text PRIMARY KEY, - applied_at timestamptz NOT NULL DEFAULT now() - )`); - const { rows } = await client.query<{ version: string }>("SELECT version FROM schema_migrations"); - const applied = new Set(rows.map((row) => row.version)); - for (const migration of migrations) { - if (applied.has(migration.version)) continue; - try { - await client.query("BEGIN"); - await client.query(migration.sql); - await client.query("INSERT INTO schema_migrations (version) VALUES ($1)", [migration.version]); - await client.query("COMMIT"); - } catch (err) { - await client.query("ROLLBACK"); - throw err; - } - console.info(`(DB) Applied migration ${migration.version}`); - } - } finally { - await client.query("SELECT pg_advisory_unlock($1)", [MIGRATION_LOCK_ID]).catch(() => undefined); - client.release(); - } -} diff --git a/apps/backend/src/game/GameClock.ts b/apps/backend/src/game/GameClock.ts deleted file mode 100644 index aa1f8cd..0000000 --- a/apps/backend/src/game/GameClock.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { GameClock, getClockTimes, Seat } from "@mancala/core"; - -// The game clock (docs/specs/game-clock). A clock is stored as each player's time at the start of the current turn -// plus when that turn started, so it only changes on moves; the time left at any moment is computed. - -export function startClock(initialMs: number, now: number): GameClock { - return { initialMs, player1Ms: initialMs, player2Ms: initialMs, turnStartedAt: now }; -} - -export function getRemainingMs(clock: GameClock, seat: Seat, runningSeat: Seat | undefined, now: number): number { - const times = getClockTimes(clock, runningSeat, now); - return seat === "player1" ? times.player1Ms : times.player2Ms; -} - -// Deducts the turn's time from the mover and starts the next turn, whether it is the opponent's or an extra turn. -export function chargeMove(clock: GameClock, moverSeat: Seat, now: number): { clock: GameClock, remainingMs: number } { - const remainingMs = getRemainingMs(clock, moverSeat, moverSeat, now); - const charged = { ...clock, turnStartedAt: now }; - if (moverSeat === "player1") charged.player1Ms = remainingMs; - else charged.player2Ms = remainingMs; - return { clock: charged, remainingMs }; -} - -// Deducts the running seat's time, if any, and stops the clock. -export function stopClock(clock: GameClock, runningSeat: Seat | undefined, now: number): GameClock { - const times = getClockTimes(clock, runningSeat, now); - return { initialMs: clock.initialMs, ...times }; -} - -// Starts the running clock again from the time it had at the start of the turn, e.g. after a restart. -export function resumeClock(clock: GameClock, now: number): GameClock { - return { ...clock, turnStartedAt: now }; -} diff --git a/apps/backend/src/game/GameClockScheduler.ts b/apps/backend/src/game/GameClockScheduler.ts deleted file mode 100644 index c8a1ab1..0000000 --- a/apps/backend/src/game/GameClockScheduler.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { GameManager } from "./GameManager"; - -export const CLOCK_CHECK_INTERVAL_MS = 1_000; - -// The scheduled service that ends games whose running clock has reached zero, also when nobody is connected -// (docs/specs/game-clock, R2.2). A move that arrives late is refused by GameManager itself, so how often this runs -// only decides how soon the end is announced, never who wins. -export class GameClockScheduler { - private timer?: NodeJS.Timeout; - - constructor( - private readonly gameManager: Pick, - private readonly intervalMs = CLOCK_CHECK_INTERVAL_MS, - ) { } - - start() { - if (this.timer) return; - this.timer = setInterval(() => this.gameManager.endGamesOutOfTime(), this.intervalMs); - } - - stop() { - if (this.timer) clearInterval(this.timer); - this.timer = undefined; - } -} diff --git a/apps/backend/src/game/GameCrashManager.ts b/apps/backend/src/game/GameCrashManager.ts deleted file mode 100644 index 1f76fe8..0000000 --- a/apps/backend/src/game/GameCrashManager.ts +++ /dev/null @@ -1,38 +0,0 @@ -import fs from "fs"; -import { Game } from "@mancala/core"; -import { BackendGame } from "../models/Game"; -const crashFolder = "./crashes"; -export class GameCrashManager { - constructor() {} - - static createCrashFolderIfNotExist(): void { - if (!fs.existsSync(crashFolder)) { - fs.mkdirSync(crashFolder); - } - } - - static getTime(): string { - let date_ob = new Date(); - let date = ("0" + date_ob.getDate()).slice(-2); - let month = ("0" + (date_ob.getMonth() + 1)).slice(-2); - let year = date_ob.getFullYear(); - let hours = date_ob.getHours(); - let minutes = date_ob.getMinutes(); - let seconds = date_ob.getSeconds(); - let miliSeconds = date_ob.getMilliseconds(); - return `${year}-${month}-${date}-${hours}:${minutes}:${seconds}:${miliSeconds}`; - } - - public static logGameCrash(error: Error, game: BackendGame): string { - this.createCrashFolderIfNotExist(); - const crash = { - message: error.message, - stack: error.stack, - game, - }; - const crashStr = JSON.stringify(crash); - const crashFileName = `${game.id}::${this.getTime()}`; - fs.writeFile(`${crashFolder}/${crashFileName}`, crashStr, () => {}); - return crashFileName; - } -} diff --git a/apps/backend/src/game/GameManager.ts b/apps/backend/src/game/GameManager.ts deleted file mode 100644 index 8a67049..0000000 --- a/apps/backend/src/game/GameManager.ts +++ /dev/null @@ -1,402 +0,0 @@ -import { CommonMancalaGame } from "mancala.js"; -import { RTMT } from "../rtmt/rtmt"; -import { GameStore } from "./gamestore/GameStore"; -import { generateKey } from "../util/key_factory"; -import { - channel_cancel_new_game, - channel_game_move, - channel_leave_game, - channel_listen_game_events, - channel_new_game, - channel_on_game_crashed, - channel_on_game_end, - channel_on_game_start, - channel_on_game_update, - channel_on_game_user_leave, - channel_on_new_game_rejected, - channel_on_user_connection_change, - channel_unlisten_game_events, - Game, - GameEndMessage, - GameMove, - GameUsersConnectionInfo, - getSeat, - Seat, - UserConnectionInfo, -} from "@mancala/core"; -import { GameCrashManager } from "./GameCrashManager"; -import { MatchMaker } from "../matchmaker/MatchMaker"; -import { BackendGame, PlayerInfo } from "../models/Game"; -import { LiveGameStore } from "../live/LiveGameStore"; -import { GameRecorder } from "./GameRecorder"; -import { fromSnapshot, GameEnding, getResult, isLiveGameSnapshot, toGameRecord, toSnapshot } from "./GameSerialization"; -import { chargeMove, getRemainingMs, startClock, stopClock } from "./GameClock"; -import { GAME_CLOCK_MS } from "../consts/config"; - -// What players and spectators receive; leaves out spectator ids and other server-side state. -export type ClientGame = Game & { - startedAt: number, moveTimes: number[], spectatorCount: number, clock: NonNullable, serverTime: number, -}; - -export class GameManager { - gameStore: GameStore; - rtmt: RTMT; - matchMaker: MatchMaker; - private readonly liveGameStore: LiveGameStore; - private readonly recorder: GameRecorder; - private readonly clock: () => number; - // Time each player has for a new game. - private readonly gameClockMs: number; - private readonly liveGamesListeners: (() => void)[] = []; - - constructor(params: { - gameStore: GameStore, - rtmt: RTMT, - matchMaker: MatchMaker, - liveGameStore: LiveGameStore, - recorder: GameRecorder, - clock?: () => number, - gameClockMs?: number, - }) { - this.gameStore = params.gameStore; - this.rtmt = params.rtmt; - this.matchMaker = params.matchMaker; - this.liveGameStore = params.liveGameStore; - this.recorder = params.recorder; - this.clock = params.clock ?? Date.now; - this.gameClockMs = params.gameClockMs ?? GAME_CLOCK_MS; - this.initialize(); - } - - private initialize() { - this.listenOnPlayersPaired(); - this.listenRtmtMessages(); - this.listenUserConnectionChange(); - } - - // Called whenever a live game starts, changes or ends. - public onLiveGamesChanged(listener: () => void) { - this.liveGamesListeners.push(listener); - } - - private listenRtmtMessages() { - this.rtmt.listenMessage(channel_new_game, (userKey: string, message: Object) => { - const activeGame = this.gameStore.getGameByUser(userKey); - if (activeGame) { - this.rtmt.sendMessage(userKey, channel_on_new_game_rejected, { gameId: activeGame.id }); - return; - } - this.matchMaker.join(userKey); - }); - - this.rtmt.listenMessage(channel_cancel_new_game, (userKey: string, message: Object) => { - this.matchMaker.cancel(userKey); - }); - - this.rtmt.listenMessage(channel_game_move, (userKey: string, message: Object) => { - this.onGameMove(userKey, message as GameMove); - }); - - this.rtmt.listenMessage(channel_leave_game, (userKey: string, message: Object) => { - this.onPlayerLeave(userKey) - }); - - this.rtmt.listenMessage(channel_listen_game_events, (userKey: string, message: Object) => { - this.addSpectator(userKey, message as string); - }); - - this.rtmt.listenMessage(channel_unlisten_game_events, (userKey: string, message: Object) => { - this.removeSpectator(userKey, message as string); - }); - } - - private onGameMove(userKey: string, gameMove: GameMove) { - const game = this.gameStore.getGameByUser(userKey); - if (game) { - try { - const now = this.clock(); - // A move that arrives after the mover's time has run out is not played. - if (userKey === game.mancalaGame.turnPlayerId && this.isOutOfTime(game, now)) { - this.endGameOnTime(game); - return; - } - const movesBefore = game.mancalaGame.history.length; - const moverSeat = getSeat(game.mancalaGame, userKey); - game.mancalaGame.moveByPlayerPit(userKey, gameMove.index); - if (game.mancalaGame.history.length > movesBefore && moverSeat) { - game.moveTimes.push(now); - const charged = chargeMove(game.clock, moverSeat, now); - game.clock = charged.clock; - game.moveRemainingMs.push(charged.remainingMs); - } - game.gameUsersConnectionInfo = this.getGameUsersConnectionInfo(game); - this.sendMessageToPlayersAndSpectators(game, channel_on_game_update, this.toClientGame(game)); - if (game.mancalaGame.state == "ended") { - this.endGame(game, { endReason: "completed", winnerId: game.mancalaGame.getWonPlayerId() }); - } else { - this.saveLiveGame(game); - this.notifyLiveGamesChanged(); - } - } catch (err: any) { - this.onGameError(game, err) - } - } else { - console.log("Game not found!"); - } - } - - private onGameError(game: BackendGame, error: any) { - console.error(error); - const crashFileName = GameCrashManager.logGameCrash(error, game); - console.info(`Game crash saved to file : ${crashFileName}`); - this.sendMessageToPlayersAndSpectators(game, channel_on_game_crashed, error); - } - - private onPlayerLeave(userKey: string) { - const game = this.gameStore.getGameByUser(userKey); - if (game) { - this.endGame(game, { endReason: "left", winnerId: this.getOpponentId(game, userKey), leftUserId: userKey }); - } - } - - // Ends a live game for any reason: stops the clock, tells everyone, forgets the game and saves it. - private endGame(game: BackendGame, ending: GameEnding) { - if (this.gameStore.get(game.id) !== game) return; - this.deleteGame(game); - game.clock = stopClock(game.clock, this.getRunningSeat(game), this.clock()); - const endMessage: GameEndMessage = { - gameId: game.id, result: getResult(game, ending), endReason: ending.endReason, clock: game.clock, - }; - if (ending.winnerId !== undefined) endMessage.winnerId = ending.winnerId; - if (ending.leftUserId !== undefined) { - endMessage.leftUserId = ending.leftUserId; - this.sendMessageToPlayersAndSpectators(game, channel_on_game_user_leave, ending.leftUserId); - } - this.sendMessageToPlayersAndSpectators(game, channel_on_game_end, endMessage); - game.spectatorIds = []; - - const record = toGameRecord(game, ending, this.clock()); - this.liveGameStore.removeGame(game.id).catch((err) => this.logStoreError(err)); - this.recorder.save(record); - this.notifyLiveGamesChanged(); - } - - // The player whose turn it is ran out of time: the opponent wins. - private endGameOnTime(game: BackendGame) { - const { turnPlayerId } = game.mancalaGame; - this.endGame(game, { endReason: "timeout", winnerId: this.getOpponentId(game, turnPlayerId) }); - } - - private isOutOfTime(game: BackendGame, now: number): boolean { - const runningSeat = this.getRunningSeat(game); - return runningSeat !== undefined && getRemainingMs(game.clock, runningSeat, runningSeat, now) <= 0; - } - - // The seat whose clock runs; undefined once the game has ended. - private getRunningSeat(game: BackendGame): Seat | undefined { - const { mancalaGame } = game; - if (mancalaGame.state === "ended" || game.clock.turnStartedAt === undefined) return undefined; - return getSeat(mancalaGame, mancalaGame.turnPlayerId); - } - - // Ends every live game whose running clock has reached zero; run by GameClockScheduler. The clock runs whether or - // not the players are online, so this also ends games that both players have left. - public endGamesOutOfTime(now = this.clock()) { - for (const game of this.gameStore.getAll()) { - try { - if (this.isOutOfTime(game, now)) this.endGameOnTime(game); - } catch (err) { - console.error(`(GameManager) Could not end game ${game.id} on time:`, err); - } - } - } - - // Puts the live games saved in Valkey back into memory, e.g. after a restart or deploy. - public async restoreLiveGames(): Promise { - let snapshots: unknown[]; - try { - snapshots = await this.liveGameStore.loadGames(); - } catch (err) { - console.error("(GameManager) Could not load live games:", (err as Error).message); - return 0; - } - let restored = 0; - for (const snapshot of snapshots) { - if (!isLiveGameSnapshot(snapshot) || this.gameStore.get(snapshot.id)) continue; - // A game that ended but whose snapshot could not be removed must not come back. - if (await this.recorder.isRecorded(snapshot.id).catch(() => false)) { - this.liveGameStore.removeGame(snapshot.id).catch((err) => this.logStoreError(err)); - continue; - } - const game = fromSnapshot(snapshot, { now: this.clock(), gameClockMs: this.gameClockMs }); - this.gameStore.set(game.id, game); - for (const playerId of [game.mancalaGame.player1Id, game.mancalaGame.player2Id]) { - this.gameStore.setGameByUser(playerId, game); - } - game.gameUsersConnectionInfo = this.getGameUsersConnectionInfo(game); - restored++; - } - if (restored > 0) { - console.info(`(GameManager) Restored ${restored} live game(s)`); - this.notifyLiveGamesChanged(); - } - return restored; - } - - private addSpectator(userId: string, gameId: string) { - const game = this.gameStore.get(gameId); - if (game) { - const isUserPlayer = this.checkUserIdisPlayer(game, userId); - const isAlreadySpectator = game.spectatorIds.find((value) => value === userId); - if (!isAlreadySpectator && !isUserPlayer) { - game.spectatorIds.push(userId); - if (!game.allSpectatorIds.includes(userId)) game.allSpectatorIds.push(userId); - game.peakSpectators = Math.max(game.peakSpectators, game.spectatorIds.length); - this.saveLiveGame(game); - this.notifyLiveGamesChanged(); - } - } - } - - private removeSpectator(userId: string, gameId: string) { - const game = this.gameStore.get(gameId); - if (game) { - const userIdIndex = game.spectatorIds.findIndex((value) => value === userId); - if (userIdIndex >= 0) { - game.spectatorIds.splice(userIdIndex, 1); - this.notifyLiveGamesChanged(); - } - } - } - - private checkUserIdisPlayer(game: BackendGame, userId: string): boolean { - return game.mancalaGame.player1Id === userId || game.mancalaGame.player2Id === userId; - } - - private getOpponentId(game: BackendGame, userId: string): string { - const { player1Id, player2Id } = game.mancalaGame; - return userId === player1Id ? player2Id : player1Id; - } - - private sendMessageToPlayersAndSpectators(game: BackendGame, channel: string, message: Object) { - const sendMessage = (userId: string) => this.rtmt.sendMessage(userId, channel, message); - sendMessage(game.mancalaGame.player1Id); - sendMessage(game.mancalaGame.player2Id); - game.spectatorIds.forEach((spectatorId) => sendMessage(spectatorId)); - } - - public toClientGame(game: BackendGame): ClientGame { - return { - id: game.id, - mancalaGame: game.mancalaGame, - gameUsersConnectionInfo: game.gameUsersConnectionInfo, - startedAt: game.startedAt, - moveTimes: game.moveTimes, - spectatorCount: game.spectatorIds.length, - clock: game.clock, - serverTime: this.clock(), - }; - } - - private listenOnPlayersPaired() { - this.matchMaker.listenOnPlayersPaired((player1Id: string, player2Id: string) => { - const game = this.createMancalaGame(player1Id, player2Id); - this.startGame(game); - }); - } - - public createMancalaGame(userKey1: string, userKey2: string): BackendGame { - const mancalaGame = new CommonMancalaGame(generateKey(), userKey1, userKey2); - const random = Math.random(); - mancalaGame.turnPlayerId = random > 0.5 ? userKey1 : userKey2; - const now = this.clock(); - - const players: Record = { - [userKey1]: this.getPlayerInfo(userKey1), - [userKey2]: this.getPlayerInfo(userKey2), - }; - const game: BackendGame = { - id: mancalaGame.id, - mancalaGame, - gameUsersConnectionInfo: this.getGameUsersConnectionInfoFromPlayers(players[userKey1], players[userKey2]), - spectatorIds: [], - allSpectatorIds: [], - peakSpectators: 0, - players, - startedAt: now, - moveTimes: [], - clock: startClock(this.gameClockMs, now), - moveRemainingMs: [], - }; - - this.gameStore.set(game.id, game); - this.gameStore.setGameByUser(userKey1, game); - this.gameStore.setGameByUser(userKey2, game); - return game; - } - - private getPlayerInfo(userId: string): PlayerInfo { - const user = this.rtmt.getUser(userId); - return { userId, name: user?.name, isGuest: user?.isGuest ?? true }; - } - - public getGameUsersConnectionInfo(game: BackendGame): GameUsersConnectionInfo { - const { player1Id, player2Id } = game.mancalaGame; - const player = (id: string): PlayerInfo => game.players[id] ?? { userId: id, isGuest: true }; - return this.getGameUsersConnectionInfoFromPlayers(player(player1Id), player(player2Id)); - } - - private getGameUsersConnectionInfoFromPlayers(player1: PlayerInfo, player2: PlayerInfo): GameUsersConnectionInfo { - const toConnectionInfo = (player: PlayerInfo): UserConnectionInfo => - ({ ...player, isOnline: this.rtmt.isClientOnline(player.userId) }); - return { - user1ConnectionInfo: toConnectionInfo(player1), - user2ConnectionInfo: toConnectionInfo(player2) - }; - } - - public startGame(game: BackendGame) { - this.sendMessageToPlayersAndSpectators(game, channel_on_game_start, this.toClientGame(game)); - this.sendUserConnectionInfo(game); - this.saveLiveGame(game); - this.notifyLiveGamesChanged(); - } - - public sendUserConnectionInfo(game: BackendGame) { - game.gameUsersConnectionInfo = this.getGameUsersConnectionInfo(game); - this.sendMessageToPlayersAndSpectators(game, channel_on_user_connection_change, game.gameUsersConnectionInfo); - } - - public deleteGame(game: BackendGame) { - if (game) { - this.gameStore.removeGameByPlayer(game.mancalaGame.player1Id); - this.gameStore.removeGameByPlayer(game.mancalaGame.player2Id); - this.gameStore.remove(game.id); - } - } - - private saveLiveGame(game: BackendGame) { - this.liveGameStore.saveGame(game.id, toSnapshot(game)).catch((err) => this.logStoreError(err)); - } - - private logStoreError(err: Error) { - console.error("(GameManager) Live game store:", err.message); - } - - private notifyLiveGamesChanged() { - this.liveGamesListeners.forEach((listener) => listener()); - } - - private listenUserConnectionChange() { - this.rtmt.listenOnClientConnectionChange((clientId: string, isOnline: boolean) => { - const game = this.gameStore.getGameByUser(clientId); - if (game) this.sendUserConnectionInfo(game); - if (!isOnline) { - const speactatingGameIds = this.gameStore.getSpeactatingGameIdsByUser(clientId); - speactatingGameIds.forEach((gameId) => this.removeSpectator(clientId, gameId)); - } else { - game && this.rtmt.sendMessage(clientId, channel_on_game_update, this.toClientGame(game)); - } - }); - } -} diff --git a/apps/backend/src/game/GameRecorder.ts b/apps/backend/src/game/GameRecorder.ts deleted file mode 100644 index 8330ed9..0000000 --- a/apps/backend/src/game/GameRecorder.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { GameRecord } from "@mancala/core"; -import { GameHistoryRepository } from "../history/GameHistoryRepository"; -import { LiveGameStore } from "../live/LiveGameStore"; - -const RETRY_INTERVAL_MS = 30_000; - -// Saves finished games. When the database is unreachable the record is kept (in Valkey, or in memory if Valkey is -// down too) and saved later, so a game is never lost because a dependency was briefly unavailable. -export class GameRecorder { - // Being saved right now. - private readonly saving = new Map(); - // Failed to save; retried later. - private readonly pending = new Map(); - private readonly savedListeners: ((record: GameRecord) => void)[] = []; - private retryTimer?: NodeJS.Timeout; - private retrying = false; - - constructor(private readonly repository: GameHistoryRepository, private readonly liveGameStore: LiveGameStore) { } - - onSaved(listener: (record: GameRecord) => void) { - this.savedListeners.push(listener); - } - - async save(record: GameRecord): Promise { - this.saving.set(record.id, record); - try { - await this.repository.save(record); - this.saving.delete(record.id); - this.notifySaved(record); - } catch (err) { - console.error(`(GameRecorder) Could not save game ${record.id}, will retry:`, (err as Error).message); - this.pending.set(record.id, record); - this.saving.delete(record.id); - await this.liveGameStore.savePendingRecord(record).catch(() => undefined); - } - } - - // Finished games that are not in the database yet, so readers can include them. - getUnsaved(): GameRecord[] { - return [...this.saving.values(), ...this.pending.values()]; - } - - // True if the game is saved or waiting to be saved. Throws if the database cannot be asked. - async isRecorded(gameId: string): Promise { - return this.pending.has(gameId) || (await this.repository.get(gameId)) !== undefined; - } - - async retryPending(): Promise { - if (this.retrying) return; - this.retrying = true; - try { - const stored = await this.liveGameStore.loadPendingRecords().catch(() => []); - stored.forEach((record) => this.pending.set(record.id, record)); - for (const record of [...this.pending.values()]) { - try { - await this.repository.save(record); - } catch (err) { - return; // still unreachable; try again later - } - this.pending.delete(record.id); - await this.liveGameStore.removePendingRecord(record.id).catch(() => undefined); - console.info(`(GameRecorder) Saved game ${record.id} after retrying`); - this.notifySaved(record); - } - } finally { - this.retrying = false; - } - } - - start() { - this.retryPending(); - this.retryTimer = setInterval(() => this.retryPending(), RETRY_INTERVAL_MS); - } - - stop() { - if (this.retryTimer) clearInterval(this.retryTimer); - } - - private notifySaved(record: GameRecord) { - this.savedListeners.forEach((listener) => listener(record)); - } -} diff --git a/apps/backend/src/game/GameSerialization.ts b/apps/backend/src/game/GameSerialization.ts deleted file mode 100644 index d88374e..0000000 --- a/apps/backend/src/game/GameSerialization.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { EndReason, GameClock, GameRecord, GameResult, MoveRecord } from "@mancala/core"; -import { CommonMancalaGame, GameState, GameStep } from "mancala.js"; -import { BackendGame, PlayerInfo } from "../models/Game"; -import { publicUserId } from "./publicIds"; -import { resumeClock, startClock } from "./GameClock"; - -type HistoryEntry = { playerId: string, moveIndex: number, boardSnapshot: number[], gameSteps: GameStep[] }; - -// What is kept in Valkey for a live game; enough to rebuild it after a restart. Version 1 has no clock. -export interface LiveGameSnapshot { - version: 1 | 2; - id: string; - player1Id: string; - player2Id: string; - turnPlayerId: string; - state: GameState; - board: number[]; - history: HistoryEntry[]; - players: Record; - startedAt: number; - moveTimes: number[]; - peakSpectators: number; - allSpectatorIds: string[]; - clock?: GameClock; - moveRemainingMs?: (number | null)[]; -} - -export interface GameEnding { - endReason: EndReason; - // Undefined when the game ends without a winner. - winnerId?: string; - leftUserId?: string; -} - -export function toSnapshot(game: BackendGame): LiveGameSnapshot { - const { mancalaGame } = game; - return { - version: 2, - id: game.id, - player1Id: mancalaGame.player1Id, - player2Id: mancalaGame.player2Id, - turnPlayerId: mancalaGame.turnPlayerId, - state: mancalaGame.state, - board: mancalaGame.board.getStoneArray(), - history: mancalaGame.history as unknown as HistoryEntry[], - players: game.players, - startedAt: game.startedAt, - moveTimes: game.moveTimes, - peakSpectators: game.peakSpectators, - allSpectatorIds: game.allSpectatorIds, - clock: game.clock, - moveRemainingMs: game.moveRemainingMs, - }; -} - -export function isLiveGameSnapshot(value: unknown): value is LiveGameSnapshot { - const snapshot = value as LiveGameSnapshot; - if (typeof snapshot !== "object" || snapshot === null) return false; - const valid = typeof snapshot.id === "string" && Array.isArray(snapshot.board) && snapshot.board.length === 14 - && Array.isArray(snapshot.history) && typeof snapshot.players === "object"; - if (snapshot.version === 1) return valid; - return snapshot.version === 2 && valid && typeof snapshot.clock?.initialMs === "number" - && Array.isArray(snapshot.moveRemainingMs); -} - -// Rebuilds a live game with real engine objects (rules, pits), so it can be played on. The running clock goes on -// from the start of the turn, so the time the backend was down is not charged; games from before the game clock -// get a new clock. -export function fromSnapshot(snapshot: LiveGameSnapshot, params: { now: number, gameClockMs: number }): BackendGame { - const mancalaGame = new CommonMancalaGame(snapshot.id, snapshot.player1Id, snapshot.player2Id); - snapshot.board.forEach((stoneCount, index) => (mancalaGame.board.pits[index].stoneCount = stoneCount)); - mancalaGame.turnPlayerId = snapshot.turnPlayerId; - mancalaGame.state = snapshot.state; - mancalaGame.history.push(...snapshot.history); - const player = (id: string): PlayerInfo => snapshot.players[id] ?? { userId: id, isGuest: true }; - return { - id: snapshot.id, - mancalaGame, - gameUsersConnectionInfo: { - user1ConnectionInfo: { ...player(snapshot.player1Id), isOnline: false }, - user2ConnectionInfo: { ...player(snapshot.player2Id), isOnline: false }, - }, - spectatorIds: [], - allSpectatorIds: snapshot.allSpectatorIds, - peakSpectators: snapshot.peakSpectators, - players: snapshot.players, - startedAt: snapshot.startedAt, - moveTimes: snapshot.moveTimes, - clock: snapshot.clock ? resumeClock(snapshot.clock, params.now) : startClock(params.gameClockMs, params.now), - moveRemainingMs: snapshot.moveRemainingMs ?? snapshot.history.map(() => null), - }; -} - -export function getResult(game: BackendGame, ending: GameEnding): GameResult | null { - const { player1Id, player2Id } = game.mancalaGame; - if (ending.winnerId === player1Id) return "player1"; - if (ending.winnerId === player2Id) return "player2"; - return ending.endReason === "completed" ? "draw" : null; -} - -// The record saved for a finished game; every id in it is a public id. -export function toGameRecord(game: BackendGame, ending: GameEnding, endedAt: number): GameRecord { - const { mancalaGame } = game; - const history = mancalaGame.history as unknown as HistoryEntry[]; - const playerSummary = (id: string) => { - const player = game.players[id]; - const summary = { userId: publicUserId(id), isGuest: player?.isGuest ?? true }; - return player?.name === undefined ? summary : { ...summary, name: player.name }; - }; - const moves: MoveRecord[] = history.map((entry, i) => { - const move: MoveRecord = { - playerId: publicUserId(entry.playerId), - moveIndex: entry.moveIndex, - boardSnapshot: entry.boardSnapshot, - gameSteps: entry.gameSteps, - at: game.moveTimes[i] ?? endedAt, - }; - const remainingMs = game.moveRemainingMs[i]; - if (typeof remainingMs === "number") move.remainingMs = remainingMs; - return move; - }); - const record: GameRecord = { - id: game.id, - player1: playerSummary(mancalaGame.player1Id), - player2: playerSummary(mancalaGame.player2Id), - board: mancalaGame.board.getStoneArray(), - result: getResult(game, ending), - endReason: ending.endReason, - startedAt: game.startedAt, - endedAt, - moveCount: moves.length, - peakSpectators: game.peakSpectators, - totalSpectators: game.allSpectatorIds.length, - moves, - clockInitialMs: game.clock.initialMs, - }; - if (ending.leftUserId !== undefined) record.leftUserId = publicUserId(ending.leftUserId); - return record; -} diff --git a/apps/backend/src/game/LiveGames.ts b/apps/backend/src/game/LiveGames.ts deleted file mode 100644 index 8884941..0000000 --- a/apps/backend/src/game/LiveGames.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - channel_on_live_games, - channel_subscribe_live_games, - channel_unsubscribe_live_games, - LiveGameSummary, -} from "@mancala/core"; -import { RTMT } from "../rtmt/rtmt"; -import { BackendGame } from "../models/Game"; -import { GameStore } from "./gamestore/GameStore"; -import { GameManager } from "./GameManager"; -import { publicUserId } from "./publicIds"; - -export const LIVE_GAMES_PUSH_COUNT = 5; -const PUSH_INTERVAL_MS = 1000; - -export function toLiveGameSummary(game: BackendGame): LiveGameSummary { - const { mancalaGame } = game; - const player = (id: string) => { - const info = game.players[id]; - const summary = { userId: publicUserId(id), isGuest: info?.isGuest ?? true }; - return info?.name === undefined ? summary : { ...summary, name: info.name }; - }; - return { - id: game.id, - player1: player(mancalaGame.player1Id), - player2: player(mancalaGame.player2Id), - board: mancalaGame.board.getStoneArray(), - turnPlayerId: publicUserId(mancalaGame.turnPlayerId), - spectatorCount: game.spectatorIds.length, - moveCount: mancalaGame.history.length, - startedAt: game.startedAt, - }; -} - -const lastActivity = (game: BackendGame) => game.moveTimes[game.moveTimes.length - 1] ?? game.startedAt; - -// Live games for the home page: most spectated first, then most recently active. Subscribers get the top games -// when they subscribe and again whenever they change, at most once per second. Subscribing does not make anyone a -// spectator. -export class LiveGames { - private readonly subscribers = new Set(); - private lastPushed = ""; - private lastPushAt = 0; - private pushTimer?: NodeJS.Timeout; - - constructor(private readonly params: { rtmt: RTMT, gameStore: GameStore, gameManager: GameManager }) { - const { rtmt, gameManager } = params; - rtmt.listenMessage(channel_subscribe_live_games, (userId: string) => this.subscribe(userId)); - rtmt.listenMessage(channel_unsubscribe_live_games, (userId: string) => this.subscribers.delete(userId)); - rtmt.listenOnClientConnectionChange((userId: string, isOnline: boolean) => { - if (!isOnline) this.subscribers.delete(userId); - }); - gameManager.onLiveGamesChanged(() => this.schedulePush()); - } - - getTop(limit: number): LiveGameSummary[] { - return this.params.gameStore.getAll() - .sort((a, b) => b.spectatorIds.length - a.spectatorIds.length || lastActivity(b) - lastActivity(a)) - .slice(0, limit) - .map(toLiveGameSummary); - } - - dispose() { - if (this.pushTimer) clearTimeout(this.pushTimer); - } - - private subscribe(userId: string) { - this.subscribers.add(userId); - this.params.rtmt.sendMessage(userId, channel_on_live_games, { games: this.getTop(LIVE_GAMES_PUSH_COUNT) }); - } - - private schedulePush() { - if (this.pushTimer) return; - const wait = Math.max(0, this.lastPushAt + PUSH_INTERVAL_MS - Date.now()); - this.pushTimer = setTimeout(() => { - this.pushTimer = undefined; - this.push(); - }, wait); - } - - private push() { - const games = this.getTop(LIVE_GAMES_PUSH_COUNT); - const serialized = JSON.stringify(games); - if (serialized === this.lastPushed) return; - this.lastPushed = serialized; - this.lastPushAt = Date.now(); - this.subscribers.forEach((userId) => this.params.rtmt.sendMessage(userId, channel_on_live_games, { games })); - } -} diff --git a/apps/backend/src/game/gamestore/GameStore.ts b/apps/backend/src/game/gamestore/GameStore.ts deleted file mode 100644 index 3e9c524..0000000 --- a/apps/backend/src/game/gamestore/GameStore.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Game } from "@mancala/core"; -import { BackendGame } from "../../models/Game"; - -export interface GameStore { - get(id: string): BackendGame | undefined; - getAll(): BackendGame[]; - set(id: string, game: BackendGame): void; - remove(id: string): void; - - setGameIdByUser(userId: string, gameId: string): void; - getGameIdByUser(userId: string): string | undefined; - removeGameIdByPlayer(userId: string): void; - - setGameByUser(userId: string, game: BackendGame): void; - getGameByUser(userId: string): BackendGame | undefined; - removeGameByPlayer(userId: string): void; - - getSpeactatingGameIdsByUser(userId: string): string[] -} \ No newline at end of file diff --git a/apps/backend/src/game/gamestore/GameStoreImpl.ts b/apps/backend/src/game/gamestore/GameStoreImpl.ts deleted file mode 100644 index 6c77908..0000000 --- a/apps/backend/src/game/gamestore/GameStoreImpl.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { GameStore } from "./GameStore"; -import { BackendGame } from "../../models/Game"; - -export class GameStoreImpl implements GameStore { - gameStore: Map = new Map() - userGameMap: Map = new Map() - - get(id: string): BackendGame | undefined { - return this.gameStore.get(id); - } - getAll(): BackendGame[] { - return [...this.gameStore.values()]; - } - set(id: string, game: BackendGame): void { - this.gameStore.set(id, game); - } - remove(id: string): void { - this.gameStore.delete(id); - } - - setGameIdByUser(userId: string, gameId: string): void { - this.userGameMap.set(userId, gameId) - } - getGameIdByUser(userId: string): string | undefined { - return this.userGameMap.get(userId); - } - removeGameIdByPlayer(userId: string): void { - this.userGameMap.delete(userId); - } - - setGameByUser(userId: string, game: BackendGame): void { - this.setGameIdByUser(userId, game.id); - } - getGameByUser(userId: string): BackendGame | undefined { - const gameId = this.getGameIdByUser(userId); - return gameId && this.gameStore.get(gameId) || undefined; - } - removeGameByPlayer(userId: string): void { - this.removeGameIdByPlayer(userId); - } - - getSpeactatingGameIdsByUser(userId: string): string[] { - const speactatingGameIds = []; - for (const game of this.gameStore.values()) { - const isSpectator = game.spectatorIds.find((value) => value === userId); - isSpectator && speactatingGameIds.push(game.id); - } - return speactatingGameIds; - } -} \ No newline at end of file diff --git a/apps/backend/src/game/publicIds.ts b/apps/backend/src/game/publicIds.ts deleted file mode 100644 index 2b3657a..0000000 --- a/apps/backend/src/game/publicIds.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createHash } from "crypto"; -import { KEYCLOAK_USER_ID_PREFIX } from "../auth"; - -const LEGACY_PUBLIC_ID_PREFIX = "legacy:"; - -// Ids of legacy userKey users are the secret they log in with, so anything saved or listed publicly uses a hash -// instead. Keycloak ids are not secret and stay as they are. -export function publicUserId(userId: string): string { - if (userId.startsWith(KEYCLOAK_USER_ID_PREFIX) || userId.startsWith(LEGACY_PUBLIC_ID_PREFIX)) return userId; - return LEGACY_PUBLIC_ID_PREFIX + createHash("sha256").update(userId).digest("hex").slice(0, 20); -} diff --git a/apps/backend/src/history/GameHistoryRepository.ts b/apps/backend/src/history/GameHistoryRepository.ts deleted file mode 100644 index 055dff9..0000000 --- a/apps/backend/src/history/GameHistoryRepository.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { FinishedGameSummary, GameRecord, MoveRecord, PlayerSummary } from "@mancala/core"; -import { Pool, PoolClient } from "pg"; - -// Saved, finished games. Ids in records are public ids (see publicIds.ts). -export interface GameHistoryRepository { - // Saving the same game twice is a no-op, so failed saves can be retried. - save(record: GameRecord): Promise; - get(id: string): Promise; - // The user's finished games, most recent first. - getRecentForUser(userId: string, limit: number): Promise; - // Finished games with the most spectators, most recent first among equals. - getPopular(limit: number): Promise; -} - -const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - -const SUMMARY_COLUMNS = `g.id, g.result, g.end_reason, g.left_user_id, g.started_at, g.ended_at, g.move_count, - g.final_board, g.peak_spectators, g.clock_initial_ms, - p1.user_id AS p1_user_id, p1.name AS p1_name, p1.is_guest AS p1_is_guest, - p2.user_id AS p2_user_id, p2.name AS p2_name, p2.is_guest AS p2_is_guest`; - -const FROM_GAMES = `FROM games g - JOIN game_players p1 ON p1.game_id = g.id AND p1.seat = 1 - JOIN game_players p2 ON p2.game_id = g.id AND p2.seat = 2`; - -type SummaryRow = { - id: string; result: FinishedGameSummary["result"]; end_reason: FinishedGameSummary["endReason"]; - left_user_id: string | null; started_at: Date; ended_at: Date; move_count: number; final_board: number[]; - peak_spectators: number; clock_initial_ms: number | null; - p1_user_id: string; p1_name: string | null; p1_is_guest: boolean; - p2_user_id: string; p2_name: string | null; p2_is_guest: boolean; -}; - -const toPlayer = (userId: string, name: string | null, isGuest: boolean): PlayerSummary => - name === null ? { userId, isGuest } : { userId, name, isGuest }; - -function toSummary(row: SummaryRow): FinishedGameSummary { - const summary: FinishedGameSummary = { - id: row.id, - player1: toPlayer(row.p1_user_id, row.p1_name, row.p1_is_guest), - player2: toPlayer(row.p2_user_id, row.p2_name, row.p2_is_guest), - board: row.final_board, - result: row.result, - endReason: row.end_reason, - startedAt: row.started_at.getTime(), - endedAt: row.ended_at.getTime(), - moveCount: row.move_count, - peakSpectators: row.peak_spectators, - }; - if (row.left_user_id !== null) summary.leftUserId = row.left_user_id; - if (row.clock_initial_ms !== null) summary.clockInitialMs = row.clock_initial_ms; - return summary; -} - -export class PostgresGameHistoryRepository implements GameHistoryRepository { - constructor(private readonly pool: Pool) { } - - async save(record: GameRecord): Promise { - await this.inTransaction(async (client) => { - const inserted = await client.query( - `INSERT INTO games (id, player1_id, player2_id, result, end_reason, left_user_id, started_at, ended_at, - move_count, final_board, moves, peak_spectators, total_spectators, clock_initial_ms) - VALUES ($1, $2, $3, $4, $5, $6, to_timestamp($7 / 1000.0), to_timestamp($8 / 1000.0), - $9, $10, $11, $12, $13, $14) - ON CONFLICT (id) DO NOTHING`, - [record.id, record.player1.userId, record.player2.userId, record.result, record.endReason, - record.leftUserId ?? null, record.startedAt, record.endedAt, record.moveCount, - JSON.stringify(record.board), JSON.stringify(record.moves), record.peakSpectators, - record.totalSpectators, record.clockInitialMs ?? null]); - if (inserted.rowCount === 0) return; - for (const [seat, player] of [[1, record.player1], [2, record.player2]] as const) { - await client.query( - "INSERT INTO game_players (game_id, seat, user_id, name, is_guest) VALUES ($1, $2, $3, $4, $5)", - [record.id, seat, player.userId, player.name ?? null, player.isGuest]); - } - }); - } - - async get(id: string): Promise { - if (!UUID_PATTERN.test(id)) return undefined; - const { rows } = await this.pool.query( - `SELECT ${SUMMARY_COLUMNS}, g.moves, g.total_spectators ${FROM_GAMES} WHERE g.id = $1`, [id]); - if (rows.length === 0) return undefined; - return { ...toSummary(rows[0]), moves: rows[0].moves, totalSpectators: rows[0].total_spectators }; - } - - async getRecentForUser(userId: string, limit: number): Promise { - const { rows } = await this.pool.query( - `SELECT ${SUMMARY_COLUMNS} ${FROM_GAMES} - WHERE g.id IN (SELECT game_id FROM game_players WHERE user_id = $1) - ORDER BY g.ended_at DESC LIMIT $2`, [userId, limit]); - return rows.map(toSummary); - } - - async getPopular(limit: number): Promise { - const { rows } = await this.pool.query( - `SELECT ${SUMMARY_COLUMNS} ${FROM_GAMES} ORDER BY g.peak_spectators DESC, g.ended_at DESC LIMIT $1`, - [limit]); - return rows.map(toSummary); - } - - private async inTransaction(work: (client: PoolClient) => Promise): Promise { - const client = await this.pool.connect(); - try { - await client.query("BEGIN"); - await work(client); - await client.query("COMMIT"); - } catch (err) { - await client.query("ROLLBACK").catch(() => undefined); - throw err; - } finally { - client.release(); - } - } -} diff --git a/apps/backend/src/history/GameHistoryService.ts b/apps/backend/src/history/GameHistoryService.ts deleted file mode 100644 index 3844590..0000000 --- a/apps/backend/src/history/GameHistoryService.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { FinishedGameSummary, GameRecord } from "@mancala/core"; -import { Cache } from "../cache/Cache"; -import { GameHistoryRepository } from "./GameHistoryRepository"; - -export const MAX_LIST_LIMIT = 20; -const CACHE_TTL_SECONDS = 10; -const POPULAR_KEY = "popular-finished"; -const recentKey = (userId: string) => `recent:${userId}`; - -// Games that ended but are not in the database yet (being saved, or waiting for a retry). -export type UnsavedGames = () => GameRecord[]; - -const toSummary = ({ moves, totalSpectators, ...summary }: GameRecord): FinishedGameSummary => summary; - -const byNewest = (a: FinishedGameSummary, b: FinishedGameSummary) => b.endedAt - a.endedAt; -const byPopularity = (a: FinishedGameSummary, b: FinishedGameSummary) => - b.peakSpectators - a.peakSpectators || byNewest(a, b); - -// Reads finished games for the API, including ones not saved yet. Lists are cached briefly and come back without the -// saved games while the database is unreachable, so the home page still loads. -export class GameHistoryService { - constructor( - private readonly repository: GameHistoryRepository, - private readonly cache: Cache, - private readonly unsaved: UnsavedGames = () => [], - ) { } - - async get(id: string): Promise { - return this.unsaved().find((record) => record.id === id) ?? this.repository.get(id); - } - - async getRecentForUser(userId: string, limit: number): Promise { - const saved = await this.cached(recentKey(userId), () => this.repository.getRecentForUser(userId, MAX_LIST_LIMIT)); - const unsaved = this.unsaved().filter((r) => r.player1.userId === userId || r.player2.userId === userId); - return merge(saved, unsaved, byNewest).slice(0, limit); - } - - async getPopular(limit: number): Promise { - const saved = await this.cached(POPULAR_KEY, () => this.repository.getPopular(MAX_LIST_LIMIT)); - return merge(saved, this.unsaved(), byPopularity).slice(0, limit); - } - - // Call when a game has been saved, so lists show it right away. - onGameSaved(record: GameRecord): Promise { - return this.cache.delete(POPULAR_KEY, recentKey(record.player1.userId), recentKey(record.player2.userId)); - } - - private async cached(key: string, load: () => Promise): Promise { - const hit = await this.cache.getJson(key); - if (hit) return hit; - try { - const games = await load(); - await this.cache.setJson(key, games, CACHE_TTL_SECONDS); - return games; - } catch (err) { - console.error("(History) Could not load games:", (err as Error).message); - return []; - } - } -} - -function merge(saved: FinishedGameSummary[], unsaved: GameRecord[], order: typeof byNewest): FinishedGameSummary[] { - const savedIds = new Set(saved.map((game) => game.id)); - return [...saved, ...unsaved.filter((record) => !savedIds.has(record.id)).map(toSummary)].sort(order); -} diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts deleted file mode 100644 index 5cde744..0000000 --- a/apps/backend/src/index.ts +++ /dev/null @@ -1,86 +0,0 @@ - -import { RTMTWS } from "./rtmt/rtmt_websocket"; -import { GameManager } from "./game/GameManager"; -import { GameStoreImpl } from "./game/gamestore/GameStoreImpl"; -import { MatchMakerImpl } from "./matchmaker/MatchMakerImpl"; -import { ExpressApp } from "./server/ExpressApp"; -import { WebServer } from "./server/WebServer"; -import { KeycloakTokenVerifier, readAuthConfig } from "./auth"; -import { createPool, runMigrations } from "./db/postgres"; -import { PostgresGameHistoryRepository } from "./history/GameHistoryRepository"; -import { createValkeyClient } from "./live/valkey"; -import { ValkeyLiveGameStore } from "./live/LiveGameStore"; -import { GameRecorder } from "./game/GameRecorder"; -import { LiveGames } from "./game/LiveGames"; -import { GameHistoryService } from "./history/GameHistoryService"; -import { ValkeyCache } from "./cache/Cache"; -import { GameClockScheduler } from "./game/GameClockScheduler"; - -const DATABASE_URL = process.env.DATABASE_URL || "postgres://mancala:mancala@localhost:5433/mancala"; -const VALKEY_URL = process.env.VALKEY_URL || "redis://localhost:6379"; -const MIGRATION_RETRY_MS = 30_000; -const VALKEY_STARTUP_WAIT_MS = 5_000; - -async function main() { - const authConfig = readAuthConfig(); - const tokenVerifier = new KeycloakTokenVerifier({ - issuer: authConfig.keycloakIssuer, - jwksUrl: authConfig.keycloakJwksUrl, - allowedClientIds: authConfig.keycloakClientIds, - }); - - const pool = createPool(DATABASE_URL); - migrateWhenReachable(pool); - const valkey = createValkeyClient(VALKEY_URL); - await waitForReady(valkey, VALKEY_STARTUP_WAIT_MS); - - const liveGameStore = new ValkeyLiveGameStore(valkey); - const repository = new PostgresGameHistoryRepository(pool); - const recorder = new GameRecorder(repository, liveGameStore); - const history = new GameHistoryService(repository, new ValkeyCache(valkey), () => recorder.getUnsaved()); - recorder.onSaved((record) => history.onGameSaved(record)); - - const rtmt = new RTMTWS({ tokenVerifier, legacyUserKeyAuth: authConfig.legacyUserKeyAuth }); - const gameStore = new GameStoreImpl(); - const matchMaker = new MatchMakerImpl({ rtmt }); - const gameManager = new GameManager({ rtmt, gameStore, matchMaker, liveGameStore, recorder }); - const liveGames = new LiveGames({ rtmt, gameStore, gameManager }); - - // Before accepting connections, so reconnecting players find their games. - await gameManager.restoreLiveGames(); - recorder.start(); - new GameClockScheduler(gameManager).start(); - - const expressApp = new ExpressApp({ - gameStore, gameManager, liveGames, history, tokenVerifier, legacyUserKeyAuth: authConfig.legacyUserKeyAuth, - }); - const server = new WebServer({ expressApp: expressApp.app }); - rtmt.initWebSocket(server.server); -} - -// The game runs without the database; saved games wait in the recorder until migrations have run. -function migrateWhenReachable(pool: ReturnType) { - runMigrations(pool).catch((err) => { - console.error(`(DB) Migrations failed, retrying in ${MIGRATION_RETRY_MS / 1000}s:`, err.message); - setTimeout(() => migrateWhenReachable(pool), MIGRATION_RETRY_MS); - }); -} - -function waitForReady(client: ReturnType, timeoutMs: number): Promise { - if (client.status === "ready") return Promise.resolve(); - return new Promise((resolve) => { - const timer = setTimeout(() => { - console.error("(Valkey) Not reachable yet; starting without restoring live games"); - resolve(); - }, timeoutMs); - client.once("ready", () => { - clearTimeout(timer); - resolve(); - }); - }); -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/apps/backend/src/live/LiveGameStore.ts b/apps/backend/src/live/LiveGameStore.ts deleted file mode 100644 index a8abcdf..0000000 --- a/apps/backend/src/live/LiveGameStore.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { GameRecord } from "@mancala/core"; -import { Valkey } from "iovalkey"; -import { scanKeys } from "./valkey"; - -const LIVE_PREFIX = "mancala:live:"; -const PENDING_SAVE_PREFIX = "mancala:pending-save:"; - -// Durable copy of state that otherwise only lives in memory: live games, so they survive a restart, and finished -// games that could not be saved to the database yet. Methods reject when Valkey is unreachable. -export interface LiveGameStore { - saveGame(gameId: string, snapshot: unknown): Promise; - removeGame(gameId: string): Promise; - loadGames(): Promise; - savePendingRecord(record: GameRecord): Promise; - removePendingRecord(gameId: string): Promise; - loadPendingRecords(): Promise; -} - -export class ValkeyLiveGameStore implements LiveGameStore { - constructor(private readonly client: Valkey) { } - - async saveGame(gameId: string, snapshot: unknown): Promise { - await this.client.set(LIVE_PREFIX + gameId, JSON.stringify(snapshot)); - } - - async removeGame(gameId: string): Promise { - await this.client.del(LIVE_PREFIX + gameId); - } - - loadGames(): Promise { - return this.loadAll(LIVE_PREFIX); - } - - async savePendingRecord(record: GameRecord): Promise { - await this.client.set(PENDING_SAVE_PREFIX + record.id, JSON.stringify(record)); - } - - async removePendingRecord(gameId: string): Promise { - await this.client.del(PENDING_SAVE_PREFIX + gameId); - } - - loadPendingRecords(): Promise { - return this.loadAll(PENDING_SAVE_PREFIX) as Promise; - } - - private async loadAll(prefix: string): Promise { - const keys = await scanKeys(this.client, prefix + "*"); - if (keys.length === 0) return []; - const values = await this.client.mget(keys); - const parsed: unknown[] = []; - values.forEach((value, i) => { - if (value === null) return; - try { - parsed.push(JSON.parse(value)); - } catch { - console.error(`(Valkey) Ignoring unreadable value in ${keys[i]}`); - } - }); - return parsed; - } -} diff --git a/apps/backend/src/live/valkey.ts b/apps/backend/src/live/valkey.ts deleted file mode 100644 index 82aee4a..0000000 --- a/apps/backend/src/live/valkey.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { RedisOptions, Valkey } from "iovalkey"; - -export function createValkeyClient(url: string, options: RedisOptions = {}): Valkey { - const client = new Valkey(url, { - // Fail commands at once while Valkey is down instead of queueing them; callers fall back. - enableOfflineQueue: false, - maxRetriesPerRequest: 1, - ...options, - }); - // Without a listener, connection errors would be unhandled 'error' events and crash the process. - let lastError = ""; - client.on("error", (err: Error) => { - if (err.message !== lastError) console.error("(Valkey) " + err.message); - lastError = err.message; - }); - client.on("ready", () => { - lastError = ""; - console.info("(Valkey) Connected"); - }); - return client; -} - -// Keys matching the pattern, without blocking Valkey the way KEYS would. -export async function scanKeys(client: Valkey, pattern: string): Promise { - const keys: string[] = []; - let cursor = "0"; - do { - const [next, batch] = await client.scan(cursor, "MATCH", pattern, "COUNT", 100); - keys.push(...batch); - cursor = next; - } while (cursor !== "0"); - return keys; -} diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/Backend.java b/apps/backend/src/main/java/app/playmancala/Backend.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/Backend.java rename to apps/backend/src/main/java/app/playmancala/Backend.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/auth/InvalidTokenException.java b/apps/backend/src/main/java/app/playmancala/auth/InvalidTokenException.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/auth/InvalidTokenException.java rename to apps/backend/src/main/java/app/playmancala/auth/InvalidTokenException.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/auth/KeycloakTokenVerifier.java b/apps/backend/src/main/java/app/playmancala/auth/KeycloakTokenVerifier.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/auth/KeycloakTokenVerifier.java rename to apps/backend/src/main/java/app/playmancala/auth/KeycloakTokenVerifier.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/auth/TokenVerifier.java b/apps/backend/src/main/java/app/playmancala/auth/TokenVerifier.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/auth/TokenVerifier.java rename to apps/backend/src/main/java/app/playmancala/auth/TokenVerifier.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/auth/TokenVerifierProducer.java b/apps/backend/src/main/java/app/playmancala/auth/TokenVerifierProducer.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/auth/TokenVerifierProducer.java rename to apps/backend/src/main/java/app/playmancala/auth/TokenVerifierProducer.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/Bank.java b/apps/backend/src/main/java/app/playmancala/engine/Bank.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/Bank.java rename to apps/backend/src/main/java/app/playmancala/engine/Bank.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/Board.java b/apps/backend/src/main/java/app/playmancala/engine/Board.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/Board.java rename to apps/backend/src/main/java/app/playmancala/engine/Board.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/CommonBoard.java b/apps/backend/src/main/java/app/playmancala/engine/CommonBoard.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/CommonBoard.java rename to apps/backend/src/main/java/app/playmancala/engine/CommonBoard.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/CommonMancalaGame.java b/apps/backend/src/main/java/app/playmancala/engine/CommonMancalaGame.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/CommonMancalaGame.java rename to apps/backend/src/main/java/app/playmancala/engine/CommonMancalaGame.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/GameRule.java b/apps/backend/src/main/java/app/playmancala/engine/GameRule.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/GameRule.java rename to apps/backend/src/main/java/app/playmancala/engine/GameRule.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/GameState.java b/apps/backend/src/main/java/app/playmancala/engine/GameState.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/GameState.java rename to apps/backend/src/main/java/app/playmancala/engine/GameState.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/GameStep.java b/apps/backend/src/main/java/app/playmancala/engine/GameStep.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/GameStep.java rename to apps/backend/src/main/java/app/playmancala/engine/GameStep.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/MancalaGame.java b/apps/backend/src/main/java/app/playmancala/engine/MancalaGame.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/MancalaGame.java rename to apps/backend/src/main/java/app/playmancala/engine/MancalaGame.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/MoveHistoryItem.java b/apps/backend/src/main/java/app/playmancala/engine/MoveHistoryItem.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/MoveHistoryItem.java rename to apps/backend/src/main/java/app/playmancala/engine/MoveHistoryItem.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/Pit.java b/apps/backend/src/main/java/app/playmancala/engine/Pit.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/Pit.java rename to apps/backend/src/main/java/app/playmancala/engine/Pit.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/PitType.java b/apps/backend/src/main/java/app/playmancala/engine/PitType.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/PitType.java rename to apps/backend/src/main/java/app/playmancala/engine/PitType.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/rules/GRClearBoardAtEnd.java b/apps/backend/src/main/java/app/playmancala/engine/rules/GRClearBoardAtEnd.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/rules/GRClearBoardAtEnd.java rename to apps/backend/src/main/java/app/playmancala/engine/rules/GRClearBoardAtEnd.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/rules/GRDoubleStoneInPit.java b/apps/backend/src/main/java/app/playmancala/engine/rules/GRDoubleStoneInPit.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/rules/GRDoubleStoneInPit.java rename to apps/backend/src/main/java/app/playmancala/engine/rules/GRDoubleStoneInPit.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/rules/GRLastStoneInBank.java b/apps/backend/src/main/java/app/playmancala/engine/rules/GRLastStoneInBank.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/rules/GRLastStoneInBank.java rename to apps/backend/src/main/java/app/playmancala/engine/rules/GRLastStoneInBank.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/engine/rules/GRLastStoneInEmptyPit.java b/apps/backend/src/main/java/app/playmancala/engine/rules/GRLastStoneInEmptyPit.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/engine/rules/GRLastStoneInEmptyPit.java rename to apps/backend/src/main/java/app/playmancala/engine/rules/GRLastStoneInEmptyPit.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/BackendGame.java b/apps/backend/src/main/java/app/playmancala/game/BackendGame.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/BackendGame.java rename to apps/backend/src/main/java/app/playmancala/game/BackendGame.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/CrashLog.java b/apps/backend/src/main/java/app/playmancala/game/CrashLog.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/CrashLog.java rename to apps/backend/src/main/java/app/playmancala/game/CrashLog.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/GameClockScheduler.java b/apps/backend/src/main/java/app/playmancala/game/GameClockScheduler.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/GameClockScheduler.java rename to apps/backend/src/main/java/app/playmancala/game/GameClockScheduler.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/GameClocks.java b/apps/backend/src/main/java/app/playmancala/game/GameClocks.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/GameClocks.java rename to apps/backend/src/main/java/app/playmancala/game/GameClocks.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/GameManager.java b/apps/backend/src/main/java/app/playmancala/game/GameManager.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/GameManager.java rename to apps/backend/src/main/java/app/playmancala/game/GameManager.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/GameRecorder.java b/apps/backend/src/main/java/app/playmancala/game/GameRecorder.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/GameRecorder.java rename to apps/backend/src/main/java/app/playmancala/game/GameRecorder.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/GameSerialization.java b/apps/backend/src/main/java/app/playmancala/game/GameSerialization.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/GameSerialization.java rename to apps/backend/src/main/java/app/playmancala/game/GameSerialization.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/GameStore.java b/apps/backend/src/main/java/app/playmancala/game/GameStore.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/GameStore.java rename to apps/backend/src/main/java/app/playmancala/game/GameStore.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/LiveGames.java b/apps/backend/src/main/java/app/playmancala/game/LiveGames.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/LiveGames.java rename to apps/backend/src/main/java/app/playmancala/game/LiveGames.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/MatchMaker.java b/apps/backend/src/main/java/app/playmancala/game/MatchMaker.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/MatchMaker.java rename to apps/backend/src/main/java/app/playmancala/game/MatchMaker.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/game/PublicIds.java b/apps/backend/src/main/java/app/playmancala/game/PublicIds.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/game/PublicIds.java rename to apps/backend/src/main/java/app/playmancala/game/PublicIds.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/history/GameHistoryRepository.java b/apps/backend/src/main/java/app/playmancala/history/GameHistoryRepository.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/history/GameHistoryRepository.java rename to apps/backend/src/main/java/app/playmancala/history/GameHistoryRepository.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/history/GameHistoryService.java b/apps/backend/src/main/java/app/playmancala/history/GameHistoryService.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/history/GameHistoryService.java rename to apps/backend/src/main/java/app/playmancala/history/GameHistoryService.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/history/Migrations.java b/apps/backend/src/main/java/app/playmancala/history/Migrations.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/history/Migrations.java rename to apps/backend/src/main/java/app/playmancala/history/Migrations.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/history/PostgresGameHistoryRepository.java b/apps/backend/src/main/java/app/playmancala/history/PostgresGameHistoryRepository.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/history/PostgresGameHistoryRepository.java rename to apps/backend/src/main/java/app/playmancala/history/PostgresGameHistoryRepository.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/http/BearerAuth.java b/apps/backend/src/main/java/app/playmancala/http/BearerAuth.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/http/BearerAuth.java rename to apps/backend/src/main/java/app/playmancala/http/BearerAuth.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/http/ErrorMapper.java b/apps/backend/src/main/java/app/playmancala/http/ErrorMapper.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/http/ErrorMapper.java rename to apps/backend/src/main/java/app/playmancala/http/ErrorMapper.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/http/GameResource.java b/apps/backend/src/main/java/app/playmancala/http/GameResource.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/http/GameResource.java rename to apps/backend/src/main/java/app/playmancala/http/GameResource.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/live/Cache.java b/apps/backend/src/main/java/app/playmancala/live/Cache.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/live/Cache.java rename to apps/backend/src/main/java/app/playmancala/live/Cache.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/live/LiveGameStore.java b/apps/backend/src/main/java/app/playmancala/live/LiveGameStore.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/live/LiveGameStore.java rename to apps/backend/src/main/java/app/playmancala/live/LiveGameStore.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/live/ValkeyCache.java b/apps/backend/src/main/java/app/playmancala/live/ValkeyCache.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/live/ValkeyCache.java rename to apps/backend/src/main/java/app/playmancala/live/ValkeyCache.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/live/ValkeyLiveGameStore.java b/apps/backend/src/main/java/app/playmancala/live/ValkeyLiveGameStore.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/live/ValkeyLiveGameStore.java rename to apps/backend/src/main/java/app/playmancala/live/ValkeyLiveGameStore.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/loop/Cancellable.java b/apps/backend/src/main/java/app/playmancala/loop/Cancellable.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/loop/Cancellable.java rename to apps/backend/src/main/java/app/playmancala/loop/Cancellable.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/loop/GameLoop.java b/apps/backend/src/main/java/app/playmancala/loop/GameLoop.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/loop/GameLoop.java rename to apps/backend/src/main/java/app/playmancala/loop/GameLoop.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/loop/Scheduler.java b/apps/backend/src/main/java/app/playmancala/loop/Scheduler.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/loop/Scheduler.java rename to apps/backend/src/main/java/app/playmancala/loop/Scheduler.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/AuthUser.java b/apps/backend/src/main/java/app/playmancala/model/AuthUser.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/AuthUser.java rename to apps/backend/src/main/java/app/playmancala/model/AuthUser.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/Channels.java b/apps/backend/src/main/java/app/playmancala/model/Channels.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/Channels.java rename to apps/backend/src/main/java/app/playmancala/model/Channels.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/ClientGame.java b/apps/backend/src/main/java/app/playmancala/model/ClientGame.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/ClientGame.java rename to apps/backend/src/main/java/app/playmancala/model/ClientGame.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/EndReason.java b/apps/backend/src/main/java/app/playmancala/model/EndReason.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/EndReason.java rename to apps/backend/src/main/java/app/playmancala/model/EndReason.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/FinishedGameSummary.java b/apps/backend/src/main/java/app/playmancala/model/FinishedGameSummary.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/FinishedGameSummary.java rename to apps/backend/src/main/java/app/playmancala/model/FinishedGameSummary.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/GameClock.java b/apps/backend/src/main/java/app/playmancala/model/GameClock.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/GameClock.java rename to apps/backend/src/main/java/app/playmancala/model/GameClock.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/GameEndMessage.java b/apps/backend/src/main/java/app/playmancala/model/GameEndMessage.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/GameEndMessage.java rename to apps/backend/src/main/java/app/playmancala/model/GameEndMessage.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/GameRecord.java b/apps/backend/src/main/java/app/playmancala/model/GameRecord.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/GameRecord.java rename to apps/backend/src/main/java/app/playmancala/model/GameRecord.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/GameResult.java b/apps/backend/src/main/java/app/playmancala/model/GameResult.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/GameResult.java rename to apps/backend/src/main/java/app/playmancala/model/GameResult.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/GameUsersConnectionInfo.java b/apps/backend/src/main/java/app/playmancala/model/GameUsersConnectionInfo.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/GameUsersConnectionInfo.java rename to apps/backend/src/main/java/app/playmancala/model/GameUsersConnectionInfo.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/Json.java b/apps/backend/src/main/java/app/playmancala/model/Json.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/Json.java rename to apps/backend/src/main/java/app/playmancala/model/Json.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/LiveGameSummary.java b/apps/backend/src/main/java/app/playmancala/model/LiveGameSummary.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/LiveGameSummary.java rename to apps/backend/src/main/java/app/playmancala/model/LiveGameSummary.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/MoveRecord.java b/apps/backend/src/main/java/app/playmancala/model/MoveRecord.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/MoveRecord.java rename to apps/backend/src/main/java/app/playmancala/model/MoveRecord.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/PlayerSummary.java b/apps/backend/src/main/java/app/playmancala/model/PlayerSummary.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/PlayerSummary.java rename to apps/backend/src/main/java/app/playmancala/model/PlayerSummary.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/Seat.java b/apps/backend/src/main/java/app/playmancala/model/Seat.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/Seat.java rename to apps/backend/src/main/java/app/playmancala/model/Seat.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/model/UserConnectionInfo.java b/apps/backend/src/main/java/app/playmancala/model/UserConnectionInfo.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/model/UserConnectionInfo.java rename to apps/backend/src/main/java/app/playmancala/model/UserConnectionInfo.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/rtmt/Rtmt.java b/apps/backend/src/main/java/app/playmancala/rtmt/Rtmt.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/rtmt/Rtmt.java rename to apps/backend/src/main/java/app/playmancala/rtmt/Rtmt.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/rtmt/RtmtEndpoint.java b/apps/backend/src/main/java/app/playmancala/rtmt/RtmtEndpoint.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/rtmt/RtmtEndpoint.java rename to apps/backend/src/main/java/app/playmancala/rtmt/RtmtEndpoint.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/rtmt/RtmtServer.java b/apps/backend/src/main/java/app/playmancala/rtmt/RtmtServer.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/rtmt/RtmtServer.java rename to apps/backend/src/main/java/app/playmancala/rtmt/RtmtServer.java diff --git a/apps/backend-quarkus/src/main/java/app/playmancala/rtmt/RtmtSocket.java b/apps/backend/src/main/java/app/playmancala/rtmt/RtmtSocket.java similarity index 100% rename from apps/backend-quarkus/src/main/java/app/playmancala/rtmt/RtmtSocket.java rename to apps/backend/src/main/java/app/playmancala/rtmt/RtmtSocket.java diff --git a/apps/backend-quarkus/src/main/resources/application.properties b/apps/backend/src/main/resources/application.properties similarity index 100% rename from apps/backend-quarkus/src/main/resources/application.properties rename to apps/backend/src/main/resources/application.properties diff --git a/apps/backend-quarkus/src/main/resources/db/migrations/001_games.sql b/apps/backend/src/main/resources/db/migrations/001_games.sql similarity index 100% rename from apps/backend-quarkus/src/main/resources/db/migrations/001_games.sql rename to apps/backend/src/main/resources/db/migrations/001_games.sql diff --git a/apps/backend-quarkus/src/main/resources/db/migrations/002_clock.sql b/apps/backend/src/main/resources/db/migrations/002_clock.sql similarity index 100% rename from apps/backend-quarkus/src/main/resources/db/migrations/002_clock.sql rename to apps/backend/src/main/resources/db/migrations/002_clock.sql diff --git a/apps/backend/src/matchmaker/MatchMaker.ts b/apps/backend/src/matchmaker/MatchMaker.ts deleted file mode 100644 index 737bf37..0000000 --- a/apps/backend/src/matchmaker/MatchMaker.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type OnPlayersPaired = (player1Id: string, player2Id: string)=> void; - -export interface MatchMaker { - join(playerId : string): void; - cancel(playerId : string): boolean; - listenOnPlayersPaired(onPlayersPaired: OnPlayersPaired ) : void; -} \ No newline at end of file diff --git a/apps/backend/src/matchmaker/MatchMakerImpl.ts b/apps/backend/src/matchmaker/MatchMakerImpl.ts deleted file mode 100644 index ec4faf9..0000000 --- a/apps/backend/src/matchmaker/MatchMakerImpl.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { RTMT } from "../rtmt/rtmt"; -import { MatchMaker, OnPlayersPaired } from "./MatchMaker"; - -export class MatchMakerImpl implements MatchMaker { - private waitingPlayerId?: string; - - private onPlayersPaired?: OnPlayersPaired; - - private rtmt: RTMT; - - constructor(params: { rtmt: RTMT }) { - this.rtmt = params.rtmt; - } - - private fireOnPlayerPaired(player1Id: string, player2Id: string): void { - this.onPlayersPaired?.(player1Id, player2Id) - } - - private setWaitingPlayerId(playerKey?: string) { - this.waitingPlayerId = playerKey; - } - - public join(newcomerPlayerId: string): void { - if (this.waitingPlayerId === newcomerPlayerId) return; - if (this.waitingPlayerId && this.rtmt.isClientOnline(this.waitingPlayerId)) { - this.fireOnPlayerPaired(this.waitingPlayerId, newcomerPlayerId) - this.setWaitingPlayerId(undefined); - } else { - this.waitingPlayerId = newcomerPlayerId - } - } - - public cancel(playerId: string): boolean { - if(playerId === this.waitingPlayerId) { - this.waitingPlayerId = undefined; - return true; - } - - return false; - } - - public listenOnPlayersPaired(onPlayersPaired: OnPlayersPaired): void { - this.onPlayersPaired = onPlayersPaired; - } -} \ No newline at end of file diff --git a/apps/backend/src/models/Game.ts b/apps/backend/src/models/Game.ts deleted file mode 100644 index c9fd6ff..0000000 --- a/apps/backend/src/models/Game.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Game, GameClock } from "@mancala/core"; - -export interface PlayerInfo { - userId: string; - // Undefined for legacy userKey clients. - name?: string; - isGuest: boolean; -} - -export interface BackendGame extends Game { - // Users watching right now. - spectatorIds: string[]; - // Everyone who watched at some point, and the most at once. - allSpectatorIds: string[]; - peakSpectators: number; - // Keyed by user id, captured when the game is created. - players: Record; - startedAt: number; - // Time of each move, aligned with mancalaGame.history. - moveTimes: number[]; - clock: GameClock; - // The mover's clock after each move, aligned with mancalaGame.history; null for moves made before the game clock. - moveRemainingMs: (number | null)[]; -} diff --git a/apps/backend/src/rtmt/encode_decode_message.ts b/apps/backend/src/rtmt/encode_decode_message.ts deleted file mode 100644 index 11908db..0000000 --- a/apps/backend/src/rtmt/encode_decode_message.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type Message = { - channel: string, - message: Object, -} - -export function encode(message: Message) { - return JSON.stringify({ - channel : message.channel, - message : message.message - }); -} - -export function decode(bytes: string): Message { - return JSON.parse(bytes); -} diff --git a/apps/backend/src/rtmt/rtmt.ts b/apps/backend/src/rtmt/rtmt.ts deleted file mode 100644 index adc97c0..0000000 --- a/apps/backend/src/rtmt/rtmt.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AuthUser } from "../auth"; - -export type OnMessage = (clientID: string, message: Object) => any -export type OnClientConnectionChange = (clientID: string, isOnline : boolean) => any - -export interface RTMT { - sendMessage: (clientID: string, channel: string, message: Object) => any; - listenMessage: (channel: string, callback: OnMessage) => any; - isClientOnline(clientID: string): boolean; - getUser(clientID: string): AuthUser | undefined; - // Adds a listener; every listener is called. - listenOnClientConnectionChange(onUserConnectionChange: OnClientConnectionChange): void; -} diff --git a/apps/backend/src/rtmt/rtmt_websocket.ts b/apps/backend/src/rtmt/rtmt_websocket.ts deleted file mode 100644 index e4fd828..0000000 --- a/apps/backend/src/rtmt/rtmt_websocket.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { OnClientConnectionChange, OnMessage, RTMT } from "./rtmt" - -import WebSocket from "ws" -import * as http from 'http'; -import { channel_auth, channel_on_auth, channel_on_auth_error, channel_ping, channel_pong, CLOSE_AUTH_FAILED } from "@mancala/core"; -import { RTMT_WS_PING_INTERVAL } from "../consts/config"; -import { decode, encode, Message } from "./encode_decode_message"; -import { AuthUser, InvalidTokenError, TokenVerifier } from "../auth"; - -const DEFAULT_AUTH_TIMEOUT_MS = 5000; -const LEGACY_USER_KEY_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - -type ClientSocket = WebSocket & { - isAlive?: boolean; - // Set once the socket is authenticated. - userId?: string; - authTimeout?: NodeJS.Timeout; -}; - -export class RTMTWS implements RTMT { - - private messageChannels = new Map() - - private wsServer: WebSocket.Server | null = null - - // A user can be connected from several devices or tabs at once. - private sockets = new Map>() - - private users = new Map() - - private pingInterval?: NodeJS.Timeout = undefined; - - private readonly tokenVerifier: TokenVerifier; - - private readonly legacyUserKeyAuth: boolean; - - private readonly authTimeoutMs: number; - - private readonly connectionChangeListeners: OnClientConnectionChange[] = []; - - constructor(params: { tokenVerifier: TokenVerifier, legacyUserKeyAuth: boolean, authTimeoutMs?: number }) { - this.tokenVerifier = params.tokenVerifier; - this.legacyUserKeyAuth = params.legacyUserKeyAuth; - this.authTimeoutMs = params.authTimeoutMs ?? DEFAULT_AUTH_TIMEOUT_MS; - } - - initWebSocket(server: http.Server) { - const wsServer = new WebSocket.Server({ server }) - this.wsServer = wsServer - wsServer.on("connection", (ws: ClientSocket, req: http.IncomingMessage) => this.onConnection(ws, req)) - this.startPingInterval(wsServer); - } - - private onConnection(ws: ClientSocket, req: http.IncomingMessage) { - console.log("(RTMT) Client Connected"); - this.setWebSocketAsAlive(ws); - - ws.on("message", (data: WebSocket.Data) => { - this.onWebSocketMessage(ws, data.toString()) - }) - - ws.on("close", (code: number, reason: string) => { - console.log("(RTMT) WS Closed! code : " + code + " reason : " + reason); - if (ws.authTimeout) clearTimeout(ws.authTimeout); - this.unbindSocket(ws); - }) - - ws.on("error", (err: Error) => { - // "close" follows, which cleans up. - console.log("(RTMT) WS Closed with error! error : " + err.message); - }) - - const legacyUserKey = new URL(req.url ?? "/", "http://localhost").searchParams.get("userKey"); - if (legacyUserKey !== null) { - if (this.legacyUserKeyAuth && LEGACY_USER_KEY_PATTERN.test(legacyUserKey)) { - this.bindSocket(ws, { id: legacyUserKey, isGuest: true }); - } else { - this.rejectSocket(ws, "userKey login is not accepted"); - } - return; - } - - ws.authTimeout = setTimeout(() => { - if (!ws.userId) this.rejectSocket(ws, "authentication timeout"); - }, this.authTimeoutMs); - } - - private authenticate(ws: ClientSocket, payload: any) { - const token = payload?.token; - if (typeof token !== "string") { - this.rejectSocket(ws, "missing token"); - return; - } - this.tokenVerifier.verify(token).then((user) => { - if (ws.readyState !== WebSocket.OPEN) return; - if (ws.userId && ws.userId !== user.id) { - this.rejectSocket(ws, "token belongs to another user"); - return; - } - if (ws.authTimeout) clearTimeout(ws.authTimeout); - if (ws.userId) { - // Token refresh on an already authenticated socket. - this.users.set(user.id, user); - } else { - this.bindSocket(ws, user); - } - this.sendMessageInternal(ws, channel_on_auth, { user }); - }).catch((err) => { - if (!(err instanceof InvalidTokenError)) console.error("(RTMT) Token verification failed", err); - this.rejectSocket(ws, err instanceof InvalidTokenError ? "invalid token" : "authentication failed"); - }); - } - - private rejectSocket(ws: ClientSocket, reason: string) { - if (ws.readyState !== WebSocket.OPEN) return; - this.sendMessageInternal(ws, channel_on_auth_error, { reason }); - ws.close(CLOSE_AUTH_FAILED, reason); - } - - private bindSocket(ws: ClientSocket, user: AuthUser) { - ws.userId = user.id; - this.users.set(user.id, user); - let userSockets = this.sockets.get(user.id); - if (!userSockets) { - userSockets = new Set(); - this.sockets.set(user.id, userSockets); - } - userSockets.add(ws); - // Fired for every new socket so each device gets the current game state. - this.connectionChangeListeners.forEach((listener) => listener(user.id, true)); - } - - private unbindSocket(ws: ClientSocket) { - const userId = ws.userId; - if (!userId) return; - const userSockets = this.sockets.get(userId); - if (!userSockets || !userSockets.delete(ws)) return; - if (userSockets.size === 0) { - this.sockets.delete(userId); - this.users.delete(userId); - this.connectionChangeListeners.forEach((listener) => listener(userId, false)); - } - } - - dispose() { - this.stopPingInterval(); - } - - sendMessage(clientID: string, channel: string, message: Object) { - if (this.wsServer) { - const userSockets = this.sockets.get(clientID) - if (userSockets) { - userSockets.forEach((ws) => this.sendMessageInternal(ws, channel, message)); - } else { - console.log("(RTMT) Client not connected!"); - } - } else { - console.log('(RTMT) ws is undefined') - } - } - - sendMessageInternal(ws: WebSocket, channel: string, message: Object) { - if (ws.readyState !== WebSocket.OPEN) return; - const data = encode({ channel: channel, message: message }) - ws.send(data) - } - - listenMessage(channel: string, callback: OnMessage) { - this.messageChannels.set(channel, callback) - } - - private onWebSocketMessage(ws: ClientSocket, messageBytes: string) { - let message: Message; - try { - message = decode(messageBytes); - } catch (err) { - console.log("(RTMT) Ignoring malformed message"); - return; - } - if (!message || typeof message.channel !== "string") return; - - if (message.channel === channel_pong) { - this.setWebSocketAsAlive(ws); - return; - } - if (message.channel === channel_auth) { - this.authenticate(ws, message.message); - return; - } - if (!ws.userId) { - console.log("(RTMT) Ignoring message from unauthenticated client on channel " + message.channel); - return; - } - this.onMessage(ws.userId, message.channel, message.message); - } - - onMessage(clientID: string, channel: string, message: Object) { - const callback = this.messageChannels.get(channel) - if (callback) { - callback(clientID, message) - } else { - console.log("(RTMT) Channel callback not found! " + channel); - } - } - - startPingInterval(wsServer: WebSocket.Server) { - this.stopPingInterval(); - this.pingInterval = setInterval(() => { - wsServer.clients.forEach((ws: ClientSocket) => { - if (ws.isAlive === false) { - console.log("(RTMT) Terminated WebSocket Connection"); - return ws.terminate(); - } - ws.isAlive = false; - this.sendMessageInternal(ws, channel_ping, {}); - }); - }, RTMT_WS_PING_INTERVAL); - } - - stopPingInterval() { - if (this.pingInterval) clearInterval(this.pingInterval); - } - - setWebSocketAsAlive(ws: ClientSocket) { - ws.isAlive = true; - } - - isClientOnline(clientID: string): boolean { - return this.sockets.has(clientID); - } - - getUser(clientID: string): AuthUser | undefined { - return this.users.get(clientID); - } - - listenOnClientConnectionChange(onUserConnectionChange: OnClientConnectionChange) { - this.connectionChangeListeners.push(onUserConnectionChange); - } -} diff --git a/apps/backend/src/server/ExpressApp.ts b/apps/backend/src/server/ExpressApp.ts deleted file mode 100644 index c523f74..0000000 --- a/apps/backend/src/server/ExpressApp.ts +++ /dev/null @@ -1,113 +0,0 @@ -import cors from "cors"; -import express from "express"; -import { NextFunction, Request, Response } from "express"; -import morgan from "morgan"; -import fs from "fs"; -import { FinishedGameResponse } from "@mancala/core"; -import { generateKey } from "../util/key_factory"; -import { GameStore } from "../game/gamestore/GameStore"; -import { GameManager } from "../game/GameManager"; -import { LiveGames, toLiveGameSummary } from "../game/LiveGames"; -import { GameHistoryService, MAX_LIST_LIMIT } from "../history/GameHistoryService"; -import { AuthUser, TokenVerifier } from "../auth"; -import { requireUser } from "./authMiddleware"; - -type AsyncHandler = (req: Request, res: Response) => Promise; - -// Express 4 does not catch rejected promises from handlers. -const handleAsync = (handler: AsyncHandler) => (req: Request, res: Response, next: NextFunction) => - handler(req, res).catch(next); - -const parseLimit = (value: unknown, fallback: number): number => { - const limit = Number.parseInt(String(value ?? ""), 10); - return Number.isNaN(limit) ? fallback : Math.min(Math.max(limit, 1), MAX_LIST_LIMIT); -}; - -export class ExpressApp { - app: Express.Application; - private readonly params: { - gameStore: GameStore, - gameManager: GameManager, - liveGames: LiveGames, - history: GameHistoryService, - tokenVerifier: TokenVerifier, - legacyUserKeyAuth: boolean, - accessLog?: boolean, - }; - - constructor(params: ExpressApp["params"]) { - this.params = params; - this.app = this.createExpressApp(); - } - - private createAccessLogMiddleware() { - return morgan("common", { - stream: fs.createWriteStream("./access.log", { flags: "a" }), - }); - } - - public createExpressApp(): Express.Application { - const { gameStore, gameManager, liveGames, history, tokenVerifier, legacyUserKeyAuth } = this.params; - const app = express(); - app.use(cors()); - if (this.params.accessLog !== false) { - app.use(this.createAccessLogMiddleware()); - app.use(morgan("dev")); - } - app.get("/", (req: Request, res: Response) => { - res.send("Server up and running! ci test1"); - }); - // Issues userKeys for legacy clients; token clients get their identity from Keycloak. - if (legacyUserKeyAuth) { - app.get("/register/", (req: Request, res: Response) => { - res.send(generateKey()); - }); - } - - // A live game as players receive it, or the saved record of a finished game. - app.get("/game/:id", handleAsync(async (req, res) => { - const game = gameStore.get(req.params.id); - if (game) { - res.send(gameManager.toClientGame(game)); - return; - } - const record = await history.get(req.params.id); - if (record) { - const response: FinishedGameResponse = { status: "finished", record }; - res.send(response); - } else { - res.sendStatus(404); - } - })); - - // Live games if any are being played, otherwise the most spectated finished games. With kind=finished - // always the finished games, for clients that get live games over the WebSocket. - app.get("/games/popular", handleAsync(async (req, res) => { - const limit = parseLimit(req.query.limit, 5); - const live = req.query.kind === "finished" ? [] : liveGames.getTop(limit); - if (live.length > 0) { - res.send({ kind: "live", games: live }); - } else { - res.send({ kind: "finished", games: await history.getPopular(limit) }); - } - })); - - app.get("/games/me/active", requireUser(tokenVerifier), (req: Request, res: Response) => { - const user = res.locals.user as AuthUser; - const game = gameStore.getGameByUser(user.id); - res.send({ game: game ? toLiveGameSummary(game) : null }); - }); - - app.get("/games/me", requireUser(tokenVerifier), handleAsync(async (req, res) => { - const user = res.locals.user as AuthUser; - res.send({ games: await history.getRecentForUser(user.id, parseLimit(req.query.limit, 3)) }); - })); - - app.use((err: Error, req: Request, res: Response, next: NextFunction) => { - console.error(`(HTTP) ${req.method} ${req.path} failed:`, err.message); - res.sendStatus(503); - }); - - return app; - } -} diff --git a/apps/backend/src/server/WebServer.ts b/apps/backend/src/server/WebServer.ts deleted file mode 100644 index fa82ad6..0000000 --- a/apps/backend/src/server/WebServer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import express, { Request, Response } from "express"; -import * as http from "http"; - -export class WebServer { - server: http.Server; - constructor(props: { expressApp: Express.Application }) { - this.server = this.createWebServer(props.expressApp); - } - public createWebServer(expressApp: Express.Application): http.Server { - const server = http.createServer(expressApp); - const port = process.env.PORT || 5005; - server.listen(port, () => { - console.log(`Server started on port ${port}`); - }); - return server; - } -} - - - - diff --git a/apps/backend/src/server/authMiddleware.ts b/apps/backend/src/server/authMiddleware.ts deleted file mode 100644 index 8f612e6..0000000 --- a/apps/backend/src/server/authMiddleware.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NextFunction, Request, Response } from "express"; -import { AuthUser, InvalidTokenError, TokenVerifier } from "../auth"; - -// Requires an "Authorization: Bearer " header with the same Keycloak token the WebSocket uses. -// The user is available as res.locals.user. -export function requireUser(tokenVerifier: TokenVerifier) { - return async (req: Request, res: Response, next: NextFunction) => { - const match = /^Bearer (.+)$/.exec(req.headers.authorization ?? ""); - if (!match) { - res.sendStatus(401); - return; - } - try { - res.locals.user = await tokenVerifier.verify(match[1]) as AuthUser; - next(); - } catch (err) { - if (!(err instanceof InvalidTokenError)) console.error("(Auth) Token verification failed", err); - res.sendStatus(401); - } - }; -} diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/auth/KeycloakTokenVerifierTest.java b/apps/backend/src/test/java/app/playmancala/auth/KeycloakTokenVerifierTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/auth/KeycloakTokenVerifierTest.java rename to apps/backend/src/test/java/app/playmancala/auth/KeycloakTokenVerifierTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/engine/EngineParityTest.java b/apps/backend/src/test/java/app/playmancala/engine/EngineParityTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/engine/EngineParityTest.java rename to apps/backend/src/test/java/app/playmancala/engine/EngineParityTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/engine/MancalaGameTest.java b/apps/backend/src/test/java/app/playmancala/engine/MancalaGameTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/engine/MancalaGameTest.java rename to apps/backend/src/test/java/app/playmancala/engine/MancalaGameTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/fakes/FakeRtmt.java b/apps/backend/src/test/java/app/playmancala/fakes/FakeRtmt.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/fakes/FakeRtmt.java rename to apps/backend/src/test/java/app/playmancala/fakes/FakeRtmt.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/fakes/InMemoryCache.java b/apps/backend/src/test/java/app/playmancala/fakes/InMemoryCache.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/fakes/InMemoryCache.java rename to apps/backend/src/test/java/app/playmancala/fakes/InMemoryCache.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/fakes/InMemoryGameHistoryRepository.java b/apps/backend/src/test/java/app/playmancala/fakes/InMemoryGameHistoryRepository.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/fakes/InMemoryGameHistoryRepository.java rename to apps/backend/src/test/java/app/playmancala/fakes/InMemoryGameHistoryRepository.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/fakes/InMemoryLiveGameStore.java b/apps/backend/src/test/java/app/playmancala/fakes/InMemoryLiveGameStore.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/fakes/InMemoryLiveGameStore.java rename to apps/backend/src/test/java/app/playmancala/fakes/InMemoryLiveGameStore.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/game/GameClockSchedulerTest.java b/apps/backend/src/test/java/app/playmancala/game/GameClockSchedulerTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/game/GameClockSchedulerTest.java rename to apps/backend/src/test/java/app/playmancala/game/GameClockSchedulerTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/game/GameManagerTest.java b/apps/backend/src/test/java/app/playmancala/game/GameManagerTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/game/GameManagerTest.java rename to apps/backend/src/test/java/app/playmancala/game/GameManagerTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/game/LiveGamesTest.java b/apps/backend/src/test/java/app/playmancala/game/LiveGamesTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/game/LiveGamesTest.java rename to apps/backend/src/test/java/app/playmancala/game/LiveGamesTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/history/GameHistoryServiceTest.java b/apps/backend/src/test/java/app/playmancala/history/GameHistoryServiceTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/history/GameHistoryServiceTest.java rename to apps/backend/src/test/java/app/playmancala/history/GameHistoryServiceTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/history/PostgresGameHistoryRepositoryTest.java b/apps/backend/src/test/java/app/playmancala/history/PostgresGameHistoryRepositoryTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/history/PostgresGameHistoryRepositoryTest.java rename to apps/backend/src/test/java/app/playmancala/history/PostgresGameHistoryRepositoryTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/http/ApiTest.java b/apps/backend/src/test/java/app/playmancala/http/ApiTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/http/ApiTest.java rename to apps/backend/src/test/java/app/playmancala/http/ApiTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/live/ValkeyStoreTest.java b/apps/backend/src/test/java/app/playmancala/live/ValkeyStoreTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/live/ValkeyStoreTest.java rename to apps/backend/src/test/java/app/playmancala/live/ValkeyStoreTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/loop/ManualScheduler.java b/apps/backend/src/test/java/app/playmancala/loop/ManualScheduler.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/loop/ManualScheduler.java rename to apps/backend/src/test/java/app/playmancala/loop/ManualScheduler.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/loop/ManualSchedulerTest.java b/apps/backend/src/test/java/app/playmancala/loop/ManualSchedulerTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/loop/ManualSchedulerTest.java rename to apps/backend/src/test/java/app/playmancala/loop/ManualSchedulerTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/rtmt/RtmtServerTest.java b/apps/backend/src/test/java/app/playmancala/rtmt/RtmtServerTest.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/rtmt/RtmtServerTest.java rename to apps/backend/src/test/java/app/playmancala/rtmt/RtmtServerTest.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/support/FakeTokenVerifier.java b/apps/backend/src/test/java/app/playmancala/support/FakeTokenVerifier.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/support/FakeTokenVerifier.java rename to apps/backend/src/test/java/app/playmancala/support/FakeTokenVerifier.java diff --git a/apps/backend-quarkus/src/test/java/app/playmancala/support/TestClient.java b/apps/backend/src/test/java/app/playmancala/support/TestClient.java similarity index 100% rename from apps/backend-quarkus/src/test/java/app/playmancala/support/TestClient.java rename to apps/backend/src/test/java/app/playmancala/support/TestClient.java diff --git a/apps/backend-quarkus/src/test/resources/engine/recorded-games.json b/apps/backend/src/test/resources/engine/recorded-games.json similarity index 100% rename from apps/backend-quarkus/src/test/resources/engine/recorded-games.json rename to apps/backend/src/test/resources/engine/recorded-games.json diff --git a/apps/backend/src/util/key_factory.ts b/apps/backend/src/util/key_factory.ts deleted file mode 100644 index fd05c2f..0000000 --- a/apps/backend/src/util/key_factory.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; - - -export const generateKey = ()=>{ - return uuidv4(); -} \ No newline at end of file diff --git a/apps/backend/tests/auth/KeycloakTokenVerifier.test.ts b/apps/backend/tests/auth/KeycloakTokenVerifier.test.ts deleted file mode 100644 index 8cbc735..0000000 --- a/apps/backend/tests/auth/KeycloakTokenVerifier.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { createLocalJWKSet, exportJWK, generateKeyPair, JWK, JWTPayload, KeyLike, SignJWT } from "jose"; -import { InvalidTokenError, KeycloakTokenVerifier } from "../../src/auth"; - -const ISSUER = "http://keycloak.test/realms/mancala"; - -describe("KeycloakTokenVerifier", () => { - let privateKey: KeyLike; - let otherPrivateKey: KeyLike; - let verifier: KeycloakTokenVerifier; - - beforeAll(async () => { - const keyPair = await generateKeyPair("RS256"); - privateKey = keyPair.privateKey; - otherPrivateKey = (await generateKeyPair("RS256")).privateKey; - const publicJwk: JWK = { ...(await exportJWK(keyPair.publicKey)), kid: "test-key", alg: "RS256" }; - verifier = new KeycloakTokenVerifier({ - issuer: ISSUER, - allowedClientIds: ["mancala-web"], - keys: createLocalJWKSet({ keys: [publicJwk] }), - }); - }); - - const sign = (claims: JWTPayload, options: { key?: KeyLike, expiresIn?: string, issuer?: string } = {}) => - new SignJWT({ typ: "Bearer", azp: "mancala-web", preferred_username: "alice", ...claims }) - .setProtectedHeader({ alg: "RS256", kid: "test-key" }) - .setSubject("user-1") - .setIssuer(options.issuer ?? ISSUER) - .setIssuedAt() - .setExpirationTime(options.expiresIn ?? "5m") - .sign(options.key ?? privateKey); - - it("returns the user of a valid access token", async () => { - const user = await verifier.verify(await sign({ realm_access: { roles: ["offline_access"] } })); - expect(user).toEqual({ id: "kc:user-1", name: "alice", isGuest: false }); - }); - - it("marks users with the guest role as guests", async () => { - const user = await verifier.verify(await sign({ preferred_username: "guest-1a2b3c4d", realm_access: { roles: ["guest"] } })); - expect(user).toEqual({ id: "kc:user-1", name: "guest-1a2b3c4d", isGuest: true }); - }); - - it.each([ - ["signed with another key", () => sign({}, { key: otherPrivateKey })], - ["from another issuer", () => sign({}, { issuer: "http://evil.test/realms/mancala" })], - ["expired", () => sign({}, { expiresIn: "-1m" })], - ["an ID token", () => sign({ typ: "ID" })], - ["a refresh token", () => sign({ typ: "Refresh" })], - ["issued to another client", () => sign({ azp: "some-other-app" })], - ["not a JWT", async () => "not-a-token"], - ])("rejects a token %s", async (_, makeToken) => { - await expect(verifier.verify(await makeToken())).rejects.toBeInstanceOf(InvalidTokenError); - }); - - it("rejects unsigned tokens", async () => { - const header = Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url"); - const payload = Buffer.from(JSON.stringify({ - iss: ISSUER, sub: "user-1", typ: "Bearer", azp: "mancala-web", exp: Math.floor(Date.now() / 1000) + 300, - })).toString("base64url"); - await expect(verifier.verify(`${header}.${payload}.`)).rejects.toBeInstanceOf(InvalidTokenError); - }); -}); diff --git a/apps/backend/tests/game/FakeRtmt.ts b/apps/backend/tests/game/FakeRtmt.ts deleted file mode 100644 index 9ce2d4f..0000000 --- a/apps/backend/tests/game/FakeRtmt.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { AuthUser } from "../../src/auth"; -import { OnClientConnectionChange, OnMessage, RTMT } from "../../src/rtmt/rtmt"; - -export type SentMessage = { clientID: string, channel: string, message: any }; - -// In-memory RTMT: tests connect users, send messages as them and inspect what the server sent back. -export class FakeRtmt implements RTMT { - readonly sent: SentMessage[] = []; - private readonly listeners = new Map(); - private readonly online = new Map(); - private readonly connectionListeners: OnClientConnectionChange[] = []; - - connect(user: AuthUser) { - this.online.set(user.id, user); - this.connectionListeners.forEach((listener) => listener(user.id, true)); - } - - disconnect(userId: string) { - this.online.delete(userId); - this.connectionListeners.forEach((listener) => listener(userId, false)); - } - - receive(userId: string, channel: string, message: Object = {}) { - const listener = this.listeners.get(channel); - if (!listener) throw new Error(`No listener for ${channel}`); - listener(userId, message); - } - - messagesTo(userId: string, channel: string): any[] { - return this.sent.filter((m) => m.clientID === userId && m.channel === channel).map((m) => m.message); - } - - sendMessage(clientID: string, channel: string, message: Object) { - // Copy, like serialising over the wire would. - this.sent.push({ clientID, channel, message: JSON.parse(JSON.stringify(message)) }); - } - - listenMessage(channel: string, callback: OnMessage) { - this.listeners.set(channel, callback); - } - - isClientOnline(clientID: string): boolean { - return this.online.has(clientID); - } - - getUser(clientID: string): AuthUser | undefined { - return this.online.get(clientID); - } - - listenOnClientConnectionChange(onUserConnectionChange: OnClientConnectionChange): void { - this.connectionListeners.push(onUserConnectionChange); - } -} diff --git a/apps/backend/tests/game/GameClockScheduler.test.ts b/apps/backend/tests/game/GameClockScheduler.test.ts deleted file mode 100644 index c5f8dce..0000000 --- a/apps/backend/tests/game/GameClockScheduler.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { GameClockScheduler } from "../../src/game/GameClockScheduler"; - -describe("GameClockScheduler", () => { - beforeEach(() => jest.useFakeTimers()); - afterEach(() => jest.useRealTimers()); - - it("ends games out of time on every interval until stopped", () => { - const gameManager = { endGamesOutOfTime: jest.fn() }; - const scheduler = new GameClockScheduler(gameManager, 1_000); - scheduler.start(); - scheduler.start(); // starting twice does not run it twice - - jest.advanceTimersByTime(999); - expect(gameManager.endGamesOutOfTime).not.toHaveBeenCalled(); - jest.advanceTimersByTime(2_001); - expect(gameManager.endGamesOutOfTime).toHaveBeenCalledTimes(3); - - scheduler.stop(); - jest.advanceTimersByTime(10_000); - expect(gameManager.endGamesOutOfTime).toHaveBeenCalledTimes(3); - }); -}); diff --git a/apps/backend/tests/game/GameManager.test.ts b/apps/backend/tests/game/GameManager.test.ts deleted file mode 100644 index 2ad8a21..0000000 --- a/apps/backend/tests/game/GameManager.test.ts +++ /dev/null @@ -1,462 +0,0 @@ -import { - channel_game_move, - channel_leave_game, - channel_listen_game_events, - channel_new_game, - channel_on_game_end, - channel_on_game_start, - channel_on_game_update, - channel_on_game_user_leave, - channel_on_new_game_rejected, - channel_on_user_connection_change, -} from "@mancala/core"; -import { GameManager } from "../../src/game/GameManager"; -import { GameRecorder } from "../../src/game/GameRecorder"; -import { GameStoreImpl } from "../../src/game/gamestore/GameStoreImpl"; -import { publicUserId } from "../../src/game/publicIds"; -import { MatchMakerImpl } from "../../src/matchmaker/MatchMakerImpl"; -import { BackendGame } from "../../src/models/Game"; -import { FakeRtmt } from "./FakeRtmt"; -import { flushPromises, InMemoryGameHistoryRepository, InMemoryLiveGameStore } from "./fakes"; - -const ALICE = { id: "kc:alice", name: "alice", isGuest: false }; -const BOB = { id: "kc:bob", name: "guest-1a2b3c4d", isGuest: true }; -const CAROL = { id: "kc:carol", name: "carol", isGuest: false }; -const DAVE = { id: "kc:dave", name: "dave", isGuest: false }; -const LEGACY = { id: "0b9f6d1e-7c1a-4f7e-9a51-3f0c2b6d8e11", isGuest: true }; -const CLOCK_MS = 5 * 60_000; - -describe("GameManager", () => { - let now: number; - let rtmt: FakeRtmt; - let gameStore: GameStoreImpl; - let liveGameStore: InMemoryLiveGameStore; - let repository: InMemoryGameHistoryRepository; - let gameManager: GameManager; - - const createManager = () => { - rtmt = new FakeRtmt(); - gameStore = new GameStoreImpl(); - const recorder = new GameRecorder(repository, liveGameStore); - gameManager = new GameManager({ - rtmt, gameStore, matchMaker: new MatchMakerImpl({ rtmt }), liveGameStore, recorder, clock: () => now, - gameClockMs: CLOCK_MS, - }); - return recorder; - }; - - beforeEach(() => { - now = Date.UTC(2026, 9, 16, 12); - liveGameStore = new InMemoryLiveGameStore(); - repository = new InMemoryGameHistoryRepository(); - createManager(); - [ALICE, BOB, CAROL, DAVE].forEach((user) => rtmt.connect(user)); - }); - - const startGame = (first = ALICE, second = BOB): BackendGame => { - rtmt.receive(first.id, channel_new_game); - rtmt.receive(second.id, channel_new_game); - const game = gameStore.getGameByUser(first.id); - if (!game) throw new Error("game did not start"); - return game; - }; - - // Plays the current player's first non-empty pit. - const playAnyMove = (game: BackendGame) => { - const { turnPlayerId } = game.mancalaGame; - const pit = [0, 1, 2, 3, 4, 5].find((p) => - game.mancalaGame.board.pits[game.mancalaGame.getBoardIndexByPlayerId(turnPlayerId, p)].stoneCount > 0)!; - rtmt.receive(turnPlayerId, channel_game_move, { index: pit }); - }; - - // Leaves the given player a single stone next to their store; playing it empties their side and ends the game. - const setUpLastMove = (game: BackendGame, playerId: string) => { - const isPlayer1 = game.mancalaGame.player1Id === playerId; - const pits = game.mancalaGame.board.pits; - pits.forEach((pit) => (pit.stoneCount = 0)); - pits[isPlayer1 ? 5 : 12].stoneCount = 1; - pits[isPlayer1 ? 7 : 0].stoneCount = 1; - game.mancalaGame.turnPlayerId = playerId; - }; - - describe("while a game is played", () => { - it("sends the connection info once when a game starts", () => { - startGame(); - expect(rtmt.messagesTo(ALICE.id, channel_on_game_start)).toHaveLength(1); - expect(rtmt.messagesTo(ALICE.id, channel_on_user_connection_change)).toHaveLength(1); - }); - - it("does not send spectator ids to clients", () => { - const game = startGame(); - rtmt.receive(CAROL.id, channel_listen_game_events, game.id); - playAnyMove(game); - const update = rtmt.messagesTo(ALICE.id, channel_on_game_update).pop(); - expect(update).not.toHaveProperty("spectatorIds"); - expect(update).not.toHaveProperty("allSpectatorIds"); - expect(update.spectatorCount).toBe(1); - }); - - it("records the time of each move", () => { - const game = startGame(); - now += 1000; - playAnyMove(game); - now += 2000; - playAnyMove(game); - expect(game.moveTimes).toEqual([game.startedAt + 1000, game.startedAt + 3000]); - expect(rtmt.messagesTo(ALICE.id, channel_on_game_update).pop().moveTimes).toHaveLength(2); - }); - - it("rejects a new game for a player who has one in progress", () => { - const game = startGame(); - rtmt.receive(ALICE.id, channel_new_game); - rtmt.receive(CAROL.id, channel_new_game); - expect(rtmt.messagesTo(ALICE.id, channel_on_new_game_rejected)).toEqual([{ gameId: game.id }]); - expect(gameStore.getGameByUser(ALICE.id)).toBe(game); - expect(gameStore.getGameByUser(CAROL.id)).toBeUndefined(); - }); - - it("counts spectators: removed on disconnect, peak and distinct total kept", async () => { - const game = startGame(); - rtmt.receive(CAROL.id, channel_listen_game_events, game.id); - rtmt.receive(DAVE.id, channel_listen_game_events, game.id); - rtmt.disconnect(CAROL.id); - rtmt.connect(CAROL); - rtmt.receive(CAROL.id, channel_listen_game_events, game.id); - rtmt.disconnect(DAVE.id); - expect(game.spectatorIds).toEqual([CAROL.id]); - expect(game.peakSpectators).toBe(2); - - rtmt.receive(ALICE.id, channel_leave_game); - await flushPromises(); - expect(repository.records.get(game.id)).toMatchObject({ peakSpectators: 2, totalSpectators: 2 }); - }); - - it("keeps a snapshot of the live game until it ends", async () => { - const game = startGame(); - playAnyMove(game); - await flushPromises(); - expect(liveGameStore.games.get(game.id)).toMatchObject({ id: game.id, moveTimes: [now] }); - - rtmt.receive(ALICE.id, channel_leave_game); - await flushPromises(); - expect(liveGameStore.games.has(game.id)).toBe(false); - }); - }); - - describe("clock", () => { - const seatOf = (game: BackendGame, playerId: string) => game.mancalaGame.player1Id === playerId ? "player1Ms" : "player2Ms"; - const opponentOf = (playerId: string) => playerId === ALICE.id ? BOB.id : ALICE.id; - - it("starts with the full time for both players, running for the player who moves first", () => { - const game = startGame(); - expect(game.clock).toEqual({ initialMs: CLOCK_MS, player1Ms: CLOCK_MS, player2Ms: CLOCK_MS, turnStartedAt: now }); - expect(rtmt.messagesTo(ALICE.id, channel_on_game_start)[0]).toMatchObject({ clock: game.clock, serverTime: now }); - }); - - it("a move deducts the turn's time from the mover only and starts the opponent's clock", () => { - const game = startGame(); - const first = game.mancalaGame.turnPlayerId; - now += 7_000; - playAnyMove(game); - expect(game.mancalaGame.turnPlayerId).toBe(opponentOf(first)); - expect(game.clock[seatOf(game, first)]).toBe(CLOCK_MS - 7_000); - expect(game.clock[seatOf(game, opponentOf(first))]).toBe(CLOCK_MS); - expect(game.clock.turnStartedAt).toBe(now); - expect(rtmt.messagesTo(BOB.id, channel_on_game_update).pop()).toMatchObject({ clock: game.clock, serverTime: now }); - - now += 3_000; - playAnyMove(game); - expect(game.clock[seatOf(game, opponentOf(first))]).toBe(CLOCK_MS - 3_000); - expect(game.moveRemainingMs).toEqual([CLOCK_MS - 7_000, CLOCK_MS - 3_000]); - }); - - it("an extra turn keeps the mover's clock running", () => { - const game = startGame(); - const mover = game.mancalaGame.turnPlayerId; - now += 2_000; - // Four stones from the fourth pit: the last one lands in the mover's store. - rtmt.receive(mover, channel_game_move, { index: 3 }); - expect(game.mancalaGame.turnPlayerId).toBe(mover); - now += 1_000; - playAnyMove(game); - expect(game.moveRemainingMs).toEqual([CLOCK_MS - 2_000, CLOCK_MS - 3_000]); - }); - - it("a move with time left is played", () => { - const game = startGame(); - now += CLOCK_MS - 1; - playAnyMove(game); - expect(game.mancalaGame.history).toHaveLength(1); - expect(game.moveRemainingMs).toEqual([1]); - }); - - it("a move that arrives after the time has run out is not played, and the opponent wins", async () => { - const game = startGame(); - const mover = game.mancalaGame.turnPlayerId; - now += CLOCK_MS; - playAnyMove(game); - await flushPromises(); - - expect(game.mancalaGame.history).toHaveLength(0); - expect(gameStore.get(game.id)).toBeUndefined(); - const winnerSeat = game.mancalaGame.player1Id === mover ? "player2" : "player1"; - expect(repository.records.get(game.id)).toMatchObject({ endReason: "timeout", result: winnerSeat }); - const end = rtmt.messagesTo(mover, channel_on_game_end)[0]; - expect(end).toMatchObject({ endReason: "timeout", winnerId: opponentOf(mover) }); - expect(end.clock).toEqual({ initialMs: CLOCK_MS, [seatOf(game, mover)]: 0, [seatOf(game, opponentOf(mover))]: CLOCK_MS }); - }); - - it("stops when the game ends, after deducting the running player's time", () => { - const game = startGame(); - const mover = game.mancalaGame.turnPlayerId; - now += 4_000; - rtmt.receive(ALICE.id, channel_leave_game); - expect(game.clock).toEqual({ - initialMs: CLOCK_MS, [seatOf(game, mover)]: CLOCK_MS - 4_000, [seatOf(game, opponentOf(mover))]: CLOCK_MS, - }); - expect(rtmt.messagesTo(BOB.id, channel_on_game_end)[0].clock).toEqual(game.clock); - }); - - it("stops when the game is completed by the rules", () => { - const game = startGame(); - playAnyMove(game); - setUpLastMove(game, ALICE.id); - now += 1_000; - rtmt.receive(ALICE.id, channel_game_move, { index: 5 }); - expect(game.clock.turnStartedAt).toBeUndefined(); - expect(rtmt.messagesTo(BOB.id, channel_on_game_end)[0].clock).toEqual(game.clock); - }); - }); - - describe("ending games on time", () => { - it("ends only the games whose running clock has reached zero, telling players and spectators", async () => { - const late = startGame(ALICE, BOB); - rtmt.receive(DAVE.id, channel_listen_game_events, late.id); - now += 60_000; - const early = startGame(CAROL, { ...DAVE, id: "kc:erin", name: "erin" }); - const latePlayer = late.mancalaGame.turnPlayerId; - - now += CLOCK_MS - 60_000 - 1; - gameManager.endGamesOutOfTime(); - expect(gameStore.get(late.id)).toBe(late); - - now += 1; - gameManager.endGamesOutOfTime(); - await flushPromises(); - expect(gameStore.get(late.id)).toBeUndefined(); - expect(gameStore.get(early.id)).toBe(early); - const winnerSeat = late.mancalaGame.player1Id === latePlayer ? "player2" : "player1"; - expect(repository.records.get(late.id)).toMatchObject({ endReason: "timeout", result: winnerSeat }); - for (const user of [ALICE, BOB, DAVE]) { - expect(rtmt.messagesTo(user.id, channel_on_game_end)).toEqual([ - expect.objectContaining({ gameId: late.id, endReason: "timeout", result: winnerSeat }), - ]); - } - }); - - it("keeps the clock running while players are offline, so games nobody comes back to end too", async () => { - const game = startGame(); - rtmt.disconnect(ALICE.id); - rtmt.disconnect(BOB.id); - now += CLOCK_MS; - gameManager.endGamesOutOfTime(); - await flushPromises(); - expect(repository.records.get(game.id)).toMatchObject({ endReason: "timeout" }); - }); - - it("no longer sends when players went offline", () => { - startGame(); - rtmt.disconnect(BOB.id); - const info = rtmt.messagesTo(ALICE.id, channel_on_user_connection_change).pop(); - expect([info.user1ConnectionInfo, info.user2ConnectionInfo].find((i: any) => i.userId === BOB.id)) - .toEqual({ userId: BOB.id, name: BOB.name, isGuest: true, isOnline: false }); - }); - }); - - describe("how games end", () => { - it("completed: the winner by the rules, with every move saved", async () => { - const game = startGame(); - playAnyMove(game); - setUpLastMove(game, ALICE.id); - rtmt.receive(ALICE.id, channel_game_move, { index: 5 }); - await flushPromises(); - - // Alice's stone goes into her store and empties her side, so she also collects Bob's last stone: 2-0. - const aliceSeat = game.mancalaGame.player1Id === ALICE.id ? "player1" : "player2"; - const record = repository.records.get(game.id)!; - expect(record.endReason).toBe("completed"); - expect(record.result).toBe(aliceSeat); - expect(record.moves).toHaveLength(2); - expect(record.moves.map((m) => m.at)).toEqual(game.moveTimes); - expect(gameStore.get(game.id)).toBeUndefined(); - expect(rtmt.messagesTo(BOB.id, channel_on_game_end)).toEqual([ - { gameId: game.id, result: aliceSeat, endReason: "completed", winnerId: ALICE.id, clock: expect.any(Object) }, - ]); - }); - - it("left: the player who leaves loses", async () => { - const game = startGame(); - rtmt.receive(CAROL.id, channel_listen_game_events, game.id); - rtmt.receive(ALICE.id, channel_leave_game); - await flushPromises(); - - const record = repository.records.get(game.id)!; - const bobSeat = game.mancalaGame.player1Id === BOB.id ? "player1" : "player2"; - expect(record).toMatchObject({ endReason: "left", result: bobSeat, leftUserId: ALICE.id }); - for (const user of [ALICE, BOB, CAROL]) { - expect(rtmt.messagesTo(user.id, channel_on_game_user_leave)).toEqual([ALICE.id]); - expect(rtmt.messagesTo(user.id, channel_on_game_end)).toEqual([ - { - gameId: game.id, result: bobSeat, endReason: "left", winnerId: BOB.id, leftUserId: ALICE.id, - clock: expect.any(Object), - }, - ]); - } - }); - - }); - - describe("saving", () => { - it("saves the initial time and each move's time left", async () => { - const game = startGame(); - now += 4_000; - playAnyMove(game); - now += 6_000; - playAnyMove(game); - rtmt.receive(ALICE.id, channel_leave_game); - await flushPromises(); - - const record = repository.records.get(game.id)!; - expect(record.clockInitialMs).toBe(CLOCK_MS); - expect(record.moves.map((move) => move.remainingMs)).toEqual([CLOCK_MS - 4_000, CLOCK_MS - 6_000]); - }); - - it("replaces legacy ids with public ids in saved games", async () => { - rtmt.connect({ id: LEGACY.id, isGuest: true }); - const game = startGame(ALICE, { ...LEGACY, name: undefined } as any); - playAnyMove(game); - rtmt.receive(LEGACY.id, channel_leave_game); - await flushPromises(); - - const saved = JSON.stringify(repository.records.get(game.id)); - expect(saved).not.toContain(LEGACY.id); - expect(saved).toContain(publicUserId(LEGACY.id)); - expect(saved).toContain(ALICE.id); - }); - - it("keeps a game that could not be saved and saves it once the database is back", async () => { - repository.failing = true; - const game = startGame(); - rtmt.receive(ALICE.id, channel_leave_game); - await flushPromises(); - expect(liveGameStore.pending.has(game.id)).toBe(true); - - // A restarted backend picks the pending record up from Valkey. - repository.failing = false; - const recorder = createManager(); - await recorder.retryPending(); - expect(repository.records.has(game.id)).toBe(true); - expect(liveGameStore.pending.has(game.id)).toBe(false); - }); - - it("keeps games playable while Valkey and the database are down", async () => { - liveGameStore.failing = true; - repository.failing = true; - const game = startGame(); - playAnyMove(game); - playAnyMove(game); - rtmt.receive(ALICE.id, channel_leave_game); - await flushPromises(); - expect(game.mancalaGame.history).toHaveLength(2); - expect(gameStore.get(game.id)).toBeUndefined(); - }); - }); - - describe("restart", () => { - it("restores live games, which can then be played on", async () => { - const game = startGame(); - playAnyMove(game); - playAnyMove(game); - await flushPromises(); - const board = game.mancalaGame.board.getStoneArray(); - const turnPlayer = game.mancalaGame.turnPlayerId; - - createManager(); - expect(await gameManager.restoreLiveGames()).toBe(1); - const restored = gameStore.getGameByUser(ALICE.id)!; - expect(restored.id).toBe(game.id); - expect(restored.mancalaGame.board.getStoneArray()).toEqual(board); - expect(restored.mancalaGame.turnPlayerId).toBe(turnPlayer); - expect(restored.mancalaGame.history).toHaveLength(2); - expect(gameStore.getGameByUser(BOB.id)).toBe(restored); - - rtmt.connect(ALICE); - rtmt.connect(BOB); - expect(rtmt.messagesTo(ALICE.id, channel_on_game_update)).toHaveLength(1); - playAnyMove(restored); - expect(restored.mancalaGame.history).toHaveLength(3); - }); - - it("restores the clock without charging the time the backend was down", async () => { - const game = startGame(); - const first = game.mancalaGame.turnPlayerId; - const firstSeat = game.mancalaGame.player1Id === first ? "player1Ms" : "player2Ms"; - const secondSeat = firstSeat === "player1Ms" ? "player2Ms" : "player1Ms"; - now += 10_000; - playAnyMove(game); - await flushPromises(); - now += 5_000; // the second player thinks, then the backend goes down for a minute - now += 60_000; - - createManager(); - await gameManager.restoreLiveGames(); - const restored = gameStore.getGameByUser(ALICE.id)!; - expect(restored.clock).toEqual({ - initialMs: CLOCK_MS, [firstSeat]: CLOCK_MS - 10_000, [secondSeat]: CLOCK_MS, turnStartedAt: now, - }); - expect(restored.moveRemainingMs).toEqual([CLOCK_MS - 10_000]); - - now += 1_000; - playAnyMove(restored); - expect(restored.clock[secondSeat]).toBe(CLOCK_MS - 1_000); - }); - - it("gives games from before the game clock a new clock", async () => { - const game = startGame(); - playAnyMove(game); - playAnyMove(game); - await flushPromises(); - const { clock, moveRemainingMs, ...rest } = liveGameStore.games.get(game.id) as any; - liveGameStore.games.set(game.id, { ...rest, version: 1 }); - - now += 60_000; - createManager(); - expect(await gameManager.restoreLiveGames()).toBe(1); - const restored = gameStore.getGameByUser(ALICE.id)!; - expect(restored.clock).toEqual({ initialMs: CLOCK_MS, player1Ms: CLOCK_MS, player2Ms: CLOCK_MS, turnStartedAt: now }); - expect(restored.moveRemainingMs).toEqual([null, null]); - - now += 2_000; - playAnyMove(restored); - rtmt.receive(ALICE.id, channel_leave_game); - await flushPromises(); - const record = repository.records.get(game.id)!; - expect(record.clockInitialMs).toBe(CLOCK_MS); - expect(record.moves.map((move) => move.remainingMs)).toEqual([undefined, undefined, CLOCK_MS - 2_000]); - }); - - it("does not bring back a game that has already been saved", async () => { - const game = startGame(); - await flushPromises(); - const snapshot = liveGameStore.games.get(game.id); - rtmt.receive(ALICE.id, channel_leave_game); - await flushPromises(); - liveGameStore.games.set(game.id, snapshot); // as if removing it had failed - - createManager(); - expect(await gameManager.restoreLiveGames()).toBe(0); - await flushPromises(); - expect(liveGameStore.games.has(game.id)).toBe(false); - }); - }); -}); diff --git a/apps/backend/tests/game/LiveGames.test.ts b/apps/backend/tests/game/LiveGames.test.ts deleted file mode 100644 index ef950a5..0000000 --- a/apps/backend/tests/game/LiveGames.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - channel_game_move, - channel_listen_game_events, - channel_new_game, - channel_on_live_games, - channel_subscribe_live_games, - channel_unsubscribe_live_games, -} from "@mancala/core"; -import { GameManager } from "../../src/game/GameManager"; -import { GameRecorder } from "../../src/game/GameRecorder"; -import { GameStoreImpl } from "../../src/game/gamestore/GameStoreImpl"; -import { LiveGames } from "../../src/game/LiveGames"; -import { MatchMakerImpl } from "../../src/matchmaker/MatchMakerImpl"; -import { FakeRtmt } from "./FakeRtmt"; -import { InMemoryGameHistoryRepository, InMemoryLiveGameStore } from "./fakes"; - -const ALICE = { id: "kc:alice", name: "alice", isGuest: false }; -const BOB = { id: "kc:bob", name: "bob", isGuest: false }; -const VIEWER = { id: "kc:viewer", name: "viewer", isGuest: false }; -const SPECTATOR = { id: "kc:spectator", name: "spectator", isGuest: false }; - -describe("LiveGames", () => { - let rtmt: FakeRtmt; - let gameStore: GameStoreImpl; - let liveGames: LiveGames; - - beforeEach(() => { - jest.useFakeTimers("modern"); - rtmt = new FakeRtmt(); - gameStore = new GameStoreImpl(); - const recorder = new GameRecorder(new InMemoryGameHistoryRepository(), new InMemoryLiveGameStore()); - const gameManager = new GameManager({ - rtmt, gameStore, matchMaker: new MatchMakerImpl({ rtmt }), liveGameStore: new InMemoryLiveGameStore(), recorder, - }); - liveGames = new LiveGames({ rtmt, gameStore, gameManager }); - [ALICE, BOB, VIEWER, SPECTATOR].forEach((user) => rtmt.connect(user)); - }); - - afterEach(() => { - liveGames.dispose(); - jest.useRealTimers(); - }); - - const pushes = () => rtmt.messagesTo(VIEWER.id, channel_on_live_games); - - const startGame = () => { - rtmt.receive(ALICE.id, channel_new_game); - rtmt.receive(BOB.id, channel_new_game); - return gameStore.getGameByUser(ALICE.id)!; - }; - - it("sends the current games on subscribe", () => { - const game = startGame(); - rtmt.receive(VIEWER.id, channel_subscribe_live_games); - expect(pushes()).toHaveLength(1); - expect(pushes()[0].games.map((g: any) => g.id)).toEqual([game.id]); - }); - - it("pushes changes at most once per second", () => { - rtmt.receive(VIEWER.id, channel_subscribe_live_games); - const game = startGame(); - jest.advanceTimersByTime(0); - expect(pushes()).toHaveLength(2); - - // Two changes right after a push are sent together, a second later. - rtmt.receive(game.mancalaGame.turnPlayerId, channel_game_move, { index: 0 }); - rtmt.receive(SPECTATOR.id, channel_listen_game_events, game.id); - jest.advanceTimersByTime(999); - expect(pushes()).toHaveLength(2); - jest.advanceTimersByTime(1); - expect(pushes()).toHaveLength(3); - expect(pushes()[2].games[0]).toMatchObject({ moveCount: 1, spectatorCount: 1 }); - }); - - it("does not count subscribers as spectators", () => { - const game = startGame(); - rtmt.receive(VIEWER.id, channel_subscribe_live_games); - expect(game.spectatorIds).toEqual([]); - expect(pushes()[0].games[0].spectatorCount).toBe(0); - }); - - it("stops pushing to users who unsubscribe or go offline", () => { - rtmt.receive(VIEWER.id, channel_subscribe_live_games); - rtmt.receive(SPECTATOR.id, channel_subscribe_live_games); - rtmt.receive(VIEWER.id, channel_unsubscribe_live_games); - rtmt.disconnect(SPECTATOR.id); - const before = rtmt.sent.length; - startGame(); - jest.advanceTimersByTime(1000); - expect(rtmt.sent.slice(before).filter((m) => m.channel === channel_on_live_games)).toEqual([]); - }); -}); diff --git a/apps/backend/tests/game/fakes.ts b/apps/backend/tests/game/fakes.ts deleted file mode 100644 index 22bc272..0000000 --- a/apps/backend/tests/game/fakes.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { FinishedGameSummary, GameRecord } from "@mancala/core"; -import { GameHistoryRepository } from "../../src/history/GameHistoryRepository"; -import { LiveGameStore } from "../../src/live/LiveGameStore"; -import { Cache } from "../../src/cache/Cache"; - -export class InMemoryLiveGameStore implements LiveGameStore { - readonly games = new Map(); - readonly pending = new Map(); - failing = false; - - private check() { - if (this.failing) return Promise.reject(new Error("Valkey is down")); - return Promise.resolve(); - } - - async saveGame(gameId: string, snapshot: unknown) { - await this.check(); - this.games.set(gameId, JSON.parse(JSON.stringify(snapshot))); - } - async removeGame(gameId: string) { - await this.check(); - this.games.delete(gameId); - } - async loadGames() { - await this.check(); - return [...this.games.values()]; - } - async savePendingRecord(record: GameRecord) { - await this.check(); - this.pending.set(record.id, record); - } - async removePendingRecord(gameId: string) { - await this.check(); - this.pending.delete(gameId); - } - async loadPendingRecords() { - await this.check(); - return [...this.pending.values()]; - } -} - -// Lists leave out the moves, like the Postgres repository. -const toSummary = ({ moves, totalSpectators, ...summary }: GameRecord): FinishedGameSummary => summary; - -export class InMemoryGameHistoryRepository implements GameHistoryRepository { - readonly records = new Map(); - failing = false; - - async save(record: GameRecord) { - if (this.failing) throw new Error("database is down"); - if (!this.records.has(record.id)) this.records.set(record.id, record); - } - async get(id: string) { - if (this.failing) throw new Error("database is down"); - return this.records.get(id); - } - async getRecentForUser(userId: string, limit: number): Promise { - return [...this.records.values()] - .filter((r) => r.player1.userId === userId || r.player2.userId === userId) - .sort((a, b) => b.endedAt - a.endedAt).slice(0, limit).map(toSummary); - } - async getPopular(limit: number): Promise { - return [...this.records.values()] - .sort((a, b) => b.peakSpectators - a.peakSpectators || b.endedAt - a.endedAt).slice(0, limit).map(toSummary); - } -} - -// Lets fire-and-forget promises (saving to Valkey or the database) settle. -export const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); - -export class InMemoryCache implements Cache { - readonly values = new Map(); - async getJson(key: string) { - return this.values.get(key) as T | undefined; - } - async setJson(key: string, value: unknown) { - this.values.set(key, JSON.parse(JSON.stringify(value))); - } - async delete(...keys: string[]) { - keys.forEach((key) => this.values.delete(key)); - } -} diff --git a/apps/backend/tests/history/PostgresGameHistoryRepository.test.ts b/apps/backend/tests/history/PostgresGameHistoryRepository.test.ts deleted file mode 100644 index 7aaecc0..0000000 --- a/apps/backend/tests/history/PostgresGameHistoryRepository.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { GameRecord } from "@mancala/core"; -import { Pool } from "pg"; -import { v4 as uuid } from "uuid"; -import { createPool, runMigrations } from "../../src/db/postgres"; -import { PostgresGameHistoryRepository } from "../../src/history/GameHistoryRepository"; -import { RunningService, startPostgres } from "../support/containers"; - -jest.setTimeout(120_000); - -const ALICE = { userId: "kc:alice", name: "alice", isGuest: false }; -const BOB = { userId: "kc:bob", name: "bob", isGuest: false }; -const GUEST = { userId: "kc:guest", name: "guest-1a2b3c4d", isGuest: true }; -const LEGACY = { userId: "legacy:0123456789abcdef0123", isGuest: true }; - -let endedAt = Date.UTC(2026, 9, 1); - -const record = (overrides: Partial = {}): GameRecord => { - endedAt += 60_000; - return { - id: uuid(), - player1: ALICE, - player2: BOB, - board: [0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 18], - result: "player1", - endReason: "completed", - startedAt: endedAt - 600_000, - endedAt, - moveCount: 1, - peakSpectators: 0, - totalSpectators: 0, - moves: [{ - playerId: ALICE.userId, moveIndex: 3, - boardSnapshot: [4, 4, 4, 0, 5, 5, 1, 4, 4, 4, 4, 4, 4, 0], - gameSteps: [{ index: 6, type: "GAME_STEP_LAST_STONE_IN_BANK", data: null }], - at: endedAt - 599_000, - }], - ...overrides, - }; -}; - -describe("PostgresGameHistoryRepository", () => { - let postgres: RunningService; - let pool: Pool; - let repository: PostgresGameHistoryRepository; - - beforeAll(async () => { - postgres = await startPostgres(); - pool = createPool(postgres.url); - await runMigrations(pool); - repository = new PostgresGameHistoryRepository(pool); - }); - - afterAll(async () => { - await pool?.end(); - await postgres?.container.stop(); - }); - - beforeEach(() => pool.query("TRUNCATE games CASCADE")); - - it("runs migrations only once", async () => { - await runMigrations(pool); - const { rows } = await pool.query("SELECT version FROM schema_migrations"); - expect(rows).toEqual([{ version: "001_games" }, { version: "002_clock" }]); - }); - - it("saves a game and reads it back unchanged", async () => { - const game = record({ player2: LEGACY, result: null, endReason: "inactive", peakSpectators: 3, totalSpectators: 5 }); - await repository.save(game); - expect(await repository.get(game.id)).toEqual(game); - }); - - it("keeps the clock: the initial time and each move's time left", async () => { - const game = record({ clockInitialMs: 300_000 }); - game.moves[0].remainingMs = 291_500; - await repository.save(game); - expect(await repository.get(game.id)).toEqual(game); - expect((await repository.getRecentForUser(ALICE.userId, 1))[0].clockInitialMs).toBe(300_000); - }); - - it("reads games saved before the game clock without clock data", async () => { - const game = record(); - await repository.save(game); - const saved = await repository.get(game.id); - expect(saved).not.toHaveProperty("clockInitialMs"); - expect(saved?.moves[0]).not.toHaveProperty("remainingMs"); - }); - - it("keeps leftUserId for games a player left", async () => { - const game = record({ result: "player2", endReason: "left", leftUserId: ALICE.userId }); - await repository.save(game); - expect((await repository.get(game.id))?.leftUserId).toBe(ALICE.userId); - }); - - it("ignores saving the same game again", async () => { - const game = record(); - await repository.save(game); - await repository.save({ ...game, result: "draw" }); - expect((await repository.get(game.id))?.result).toBe("player1"); - }); - - it("returns undefined for unknown and malformed ids", async () => { - expect(await repository.get(uuid())).toBeUndefined(); - expect(await repository.get("not-a-uuid")).toBeUndefined(); - }); - - it("lists a user's games newest first, without the moves", async () => { - const first = record(); - const second = record({ player1: GUEST, player2: ALICE }); - const otherPlayers = record({ player1: BOB, player2: GUEST }); - for (const game of [first, second, otherPlayers]) await repository.save(game); - - const recent = await repository.getRecentForUser(ALICE.userId, 3); - expect(recent.map((game) => game.id)).toEqual([second.id, first.id]); - expect(recent[0]).not.toHaveProperty("moves"); - expect(await repository.getRecentForUser(ALICE.userId, 1)).toHaveLength(1); - }); - - it("lists popular games by peak spectators, then newest", async () => { - const quiet = record({ peakSpectators: 0 }); - const popular = record({ peakSpectators: 9 }); - const alsoPopularButNewer = record({ peakSpectators: 9 }); - for (const game of [quiet, popular, alsoPopularButNewer]) await repository.save(game); - - const ids = (await repository.getPopular(5)).map((game) => game.id); - expect(ids).toEqual([alsoPopularButNewer.id, popular.id, quiet.id]); - }); -}); diff --git a/apps/backend/tests/live/ValkeyStore.test.ts b/apps/backend/tests/live/ValkeyStore.test.ts deleted file mode 100644 index 6446f26..0000000 --- a/apps/backend/tests/live/ValkeyStore.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { GameRecord } from "@mancala/core"; -import { Valkey } from "iovalkey"; -import { ValkeyCache } from "../../src/cache/Cache"; -import { ValkeyLiveGameStore } from "../../src/live/LiveGameStore"; -import { createValkeyClient } from "../../src/live/valkey"; -import { RunningService, startValkey } from "../support/containers"; - -jest.setTimeout(120_000); - -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -const record = (id: string): GameRecord => ({ - id, - player1: { userId: "kc:alice", name: "alice", isGuest: false }, - player2: { userId: "kc:bob", name: "bob", isGuest: false }, - board: [0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 18], - result: "player1", endReason: "completed", - startedAt: 1, endedAt: 2, moveCount: 0, peakSpectators: 0, totalSpectators: 0, moves: [], -}); - -describe("Valkey store and cache", () => { - let valkey: RunningService; - let client: Valkey; - - beforeAll(async () => { - valkey = await startValkey(); - client = createValkeyClient(valkey.url); - await new Promise((resolve) => client.once("ready", resolve)); - }); - - afterAll(async () => { - client?.disconnect(); - await valkey?.container.stop(); - }); - - beforeEach(() => client.flushall()); - - describe("ValkeyLiveGameStore", () => { - it("saves, loads and removes live game snapshots", async () => { - const store = new ValkeyLiveGameStore(client); - await store.saveGame("g1", { id: "g1", moves: 1 }); - await store.saveGame("g2", { id: "g2", moves: 2 }); - await store.saveGame("g1", { id: "g1", moves: 3 }); - const sortById = (games: unknown[]) => (games as { id: string }[]).sort((a, b) => a.id.localeCompare(b.id)); - expect(sortById(await store.loadGames())).toEqual([{ id: "g1", moves: 3 }, { id: "g2", moves: 2 }]); - - await store.removeGame("g1"); - expect(await store.loadGames()).toEqual([{ id: "g2", moves: 2 }]); - }); - - it("keeps pending records separate from live games", async () => { - const store = new ValkeyLiveGameStore(client); - await store.saveGame("g1", { id: "g1" }); - await store.savePendingRecord(record("r1")); - expect(await store.loadPendingRecords()).toEqual([record("r1")]); - expect(await store.loadGames()).toEqual([{ id: "g1" }]); - - await store.removePendingRecord("r1"); - expect(await store.loadPendingRecords()).toEqual([]); - }); - - it("skips values it cannot read", async () => { - const store = new ValkeyLiveGameStore(client); - await client.set("mancala:live:broken", "{not json"); - await store.saveGame("g1", { id: "g1" }); - expect(await store.loadGames()).toEqual([{ id: "g1" }]); - }); - }); - - describe("ValkeyCache", () => { - it("returns cached values until they expire", async () => { - const cache = new ValkeyCache(client); - await cache.setJson("popular", [1, 2, 3], 1); - expect(await cache.getJson("popular")).toEqual([1, 2, 3]); - await sleep(1500); - expect(await cache.getJson("popular")).toBeUndefined(); - }); - - it("deletes entries", async () => { - const cache = new ValkeyCache(client); - await cache.setJson("a", 1, 60); - await cache.setJson("b", 2, 60); - await cache.delete("a", "b"); - expect(await cache.getJson("a")).toBeUndefined(); - expect(await cache.getJson("b")).toBeUndefined(); - }); - }); - - describe("when Valkey is unreachable", () => { - let offline: Valkey; - - beforeAll(() => { - // Nothing listens on port 1. No reconnect attempts, so nothing is left running after the tests. - offline = createValkeyClient("redis://127.0.0.1:1", { retryStrategy: () => null, disconnectTimeout: 50 }); - }); - - afterAll(() => offline.disconnect()); - - it("the cache answers with misses instead of failing", async () => { - const cache = new ValkeyCache(offline); - await expect(cache.setJson("a", 1, 60)).resolves.toBeUndefined(); - await expect(cache.getJson("a")).resolves.toBeUndefined(); - await expect(cache.delete("a")).resolves.toBeUndefined(); - }); - - it("the store rejects at once, so callers can fall back", async () => { - const store = new ValkeyLiveGameStore(offline); - const started = Date.now(); - await expect(store.saveGame("g1", {})).rejects.toThrow(); - expect(Date.now() - started).toBeLessThan(1000); - }); - }); -}); diff --git a/apps/backend/tests/mancala.test.ts b/apps/backend/tests/mancala.test.ts deleted file mode 100644 index 3ff4b52..0000000 --- a/apps/backend/tests/mancala.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CommonMancalaGame, MancalaGame } from "mancala.js"; - -function createGame(player1Id: string, player2Id: string): MancalaGame { - return new CommonMancalaGame("0", player1Id, player2Id); -} - -describe("mancala", function () { - const player1 = "0"; - const player2 = "0"; - it("turn test", function () { - const game = createGame(player1, player2); - expect(game.id).toBe("0"); - }); -}); diff --git a/apps/backend/tests/rtmt/rtmt_websocket.test.ts b/apps/backend/tests/rtmt/rtmt_websocket.test.ts deleted file mode 100644 index ce1e3fa..0000000 --- a/apps/backend/tests/rtmt/rtmt_websocket.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import * as http from "http"; -import { AddressInfo } from "net"; -import WebSocket from "ws"; -import { AuthUser, InvalidTokenError, TokenVerifier } from "../../src/auth"; -import { channel_auth, channel_on_auth, channel_on_auth_error, CLOSE_AUTH_FAILED } from "@mancala/core"; -import { RTMTWS } from "../../src/rtmt/rtmt_websocket"; - -const LEGACY_KEY = "0b9f6d1e-7c1a-4f7e-9a51-3f0c2b6d8e11"; - -// Accepts tokens of the form "valid:". -const fakeVerifier: TokenVerifier = { - async verify(token: string): Promise { - if (!token.startsWith("valid:")) throw new InvalidTokenError("bad token"); - const id = token.slice("valid:".length); - return { id, name: `name-of-${id}`, isGuest: false }; - }, -}; - -type Received = { channel: string, message: any }; - -class TestClient { - readonly messages: Received[] = []; - readonly closed: Promise; - constructor(readonly ws: WebSocket) { - ws.on("message", (data) => this.messages.push(JSON.parse(data.toString()))); - this.closed = new Promise((resolve) => ws.on("close", (code) => resolve(code))); - } - send(channel: string, message: any) { - this.ws.send(JSON.stringify({ channel, message })); - } - async waitFor(channel: string): Promise { - for (let i = 0; i < 100; i++) { - const found = this.messages.find((m) => m.channel === channel); - if (found) return found.message; - await sleep(20); - } - throw new Error(`no message on ${channel}, got ${JSON.stringify(this.messages)}`); - } -} - -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -describe("RTMTWS", () => { - let server: http.Server; - let rtmt: RTMTWS; - let clients: TestClient[]; - let connectionChanges: [string, boolean][]; - - const start = async (legacyUserKeyAuth: boolean) => { - server = http.createServer(); - rtmt = new RTMTWS({ tokenVerifier: fakeVerifier, legacyUserKeyAuth, authTimeoutMs: 300 }); - const changes: [string, boolean][] = []; - connectionChanges = changes; - rtmt.listenOnClientConnectionChange((id, isOnline) => changes.push([id, isOnline])); - rtmt.initWebSocket(server); - await new Promise((resolve) => server.listen(0, resolve)); - }; - - const connect = async (query = ""): Promise => { - const port = (server.address() as AddressInfo).port; - const ws = new WebSocket(`ws://localhost:${port}/${query}`); - const client = new TestClient(ws); - clients.push(client); - await new Promise((resolve, reject) => { ws.on("open", resolve); ws.on("error", reject); }); - return client; - }; - - const connectAs = async (id: string): Promise => { - const client = await connect(); - client.send(channel_auth, { token: `valid:${id}` }); - await client.waitFor(channel_on_auth); - return client; - }; - - beforeEach(() => { clients = []; }); - - afterEach(async () => { - clients.forEach((c) => c.ws.terminate()); - rtmt.dispose(); - await new Promise((resolve) => server.close(resolve)); - }); - - describe("token auth", () => { - beforeEach(() => start(false)); - - it("authenticates a socket and routes its messages as that user", async () => { - const received: [string, any][] = []; - rtmt.listenMessage("hello", (id, message) => received.push([id, message])); - - const client = await connect(); - client.send(channel_auth, { token: "valid:alice" }); - expect(await client.waitFor(channel_on_auth)).toEqual({ user: { id: "alice", name: "name-of-alice", isGuest: false } }); - - client.send("hello", { n: 1 }); - await sleep(50); - expect(received).toEqual([["alice", { n: 1 }]]); - expect(rtmt.isClientOnline("alice")).toBe(true); - expect(rtmt.getUser("alice")?.name).toBe("name-of-alice"); - }); - - it("ignores messages before authentication", async () => { - const received: string[] = []; - rtmt.listenMessage("hello", (id) => received.push(id)); - const client = await connect(); - client.send("hello", {}); - await sleep(50); - expect(received).toEqual([]); - }); - - it("closes the socket on an invalid token", async () => { - const client = await connect(); - client.send(channel_auth, { token: "forged" }); - expect(await client.waitFor(channel_on_auth_error)).toEqual({ reason: "invalid token" }); - expect(await client.closed).toBe(CLOSE_AUTH_FAILED); - }); - - it("closes the socket when no token arrives in time", async () => { - const client = await connect(); - expect(await client.closed).toBe(CLOSE_AUTH_FAILED); - }); - - it("accepts a refreshed token for the same user but not for another user", async () => { - const client = await connectAs("alice"); - client.messages.length = 0; - client.send(channel_auth, { token: "valid:alice" }); - await client.waitFor(channel_on_auth); - - client.send(channel_auth, { token: "valid:mallory" }); - expect(await client.waitFor(channel_on_auth_error)).toEqual({ reason: "token belongs to another user" }); - expect(await client.closed).toBe(CLOSE_AUTH_FAILED); - }); - - it("rejects userKey connections when legacy auth is off", async () => { - const client = await connect(`?userKey=${LEGACY_KEY}`); - expect(await client.closed).toBe(CLOSE_AUTH_FAILED); - }); - - it("survives malformed messages", async () => { - const client = await connectAs("alice"); - client.ws.send("{not json"); - client.ws.send("null"); - client.messages.length = 0; - client.send(channel_auth, { token: "valid:alice" }); - await client.waitFor(channel_on_auth); - }); - }); - - describe("several sockets per user", () => { - beforeEach(() => start(false)); - - it("delivers messages to every socket of the user", async () => { - const phone = await connectAs("alice"); - const laptop = await connectAs("alice"); - rtmt.sendMessage("alice", "news", { n: 1 }); - expect(await phone.waitFor("news")).toEqual({ n: 1 }); - expect(await laptop.waitFor("news")).toEqual({ n: 1 }); - }); - - it("keeps the user online until the last socket closes", async () => { - const phone = await connectAs("alice"); - const laptop = await connectAs("alice"); - - phone.ws.close(); - await phone.closed; - await sleep(50); - expect(rtmt.isClientOnline("alice")).toBe(true); - expect(connectionChanges).not.toContainEqual(["alice", false]); - - laptop.ws.close(); - await laptop.closed; - await sleep(50); - expect(rtmt.isClientOnline("alice")).toBe(false); - expect(connectionChanges).toContainEqual(["alice", false]); - }); - - it("does not drop a reconnected socket when the old one closes late", async () => { - const oldSocket = await connectAs("alice"); - const newSocket = await connectAs("alice"); - oldSocket.ws.terminate(); - await oldSocket.closed; - await sleep(50); - - rtmt.sendMessage("alice", "news", {}); - expect(await newSocket.waitFor("news")).toEqual({}); - }); - }); - - describe("legacy userKey auth", () => { - beforeEach(() => start(true)); - - it("accepts a UUID userKey", async () => { - await connect(`?userKey=${LEGACY_KEY}`); - await sleep(50); - expect(rtmt.isClientOnline(LEGACY_KEY)).toBe(true); - expect(rtmt.getUser(LEGACY_KEY)).toEqual({ id: LEGACY_KEY, isGuest: true }); - }); - - it("cannot claim a Keycloak user's id", async () => { - const client = await connect("?userKey=kc:0b9f6d1e-7c1a-4f7e-9a51-3f0c2b6d8e11"); - expect(await client.closed).toBe(CLOSE_AUTH_FAILED); - }); - - it("cannot switch a legacy socket to a token identity", async () => { - const client = await connect(`?userKey=${LEGACY_KEY}`); - client.send(channel_auth, { token: "valid:kc:alice" }); - expect(await client.closed).toBe(CLOSE_AUTH_FAILED); - }); - }); -}); diff --git a/apps/backend/tests/server/ExpressApp.test.ts b/apps/backend/tests/server/ExpressApp.test.ts deleted file mode 100644 index 95a6931..0000000 --- a/apps/backend/tests/server/ExpressApp.test.ts +++ /dev/null @@ -1,210 +0,0 @@ -import * as http from "http"; -import { AddressInfo } from "net"; -import { - channel_game_move, - channel_leave_game, - channel_listen_game_events, - channel_new_game, - GameRecord, -} from "@mancala/core"; -import { AuthUser, InvalidTokenError, TokenVerifier } from "../../src/auth"; -import { GameManager } from "../../src/game/GameManager"; -import { GameRecorder } from "../../src/game/GameRecorder"; -import { GameStoreImpl } from "../../src/game/gamestore/GameStoreImpl"; -import { LiveGames } from "../../src/game/LiveGames"; -import { publicUserId } from "../../src/game/publicIds"; -import { GameHistoryService } from "../../src/history/GameHistoryService"; -import { MatchMakerImpl } from "../../src/matchmaker/MatchMakerImpl"; -import { ExpressApp } from "../../src/server/ExpressApp"; -import { FakeRtmt } from "../game/FakeRtmt"; -import { flushPromises, InMemoryCache, InMemoryGameHistoryRepository, InMemoryLiveGameStore } from "../game/fakes"; - -const ALICE = { id: "kc:alice", name: "alice", isGuest: false }; -const BOB = { id: "kc:bob", name: "bob", isGuest: false }; -const CAROL = { id: "kc:carol", name: "carol", isGuest: false }; -const DAVE = { id: "kc:dave", name: "dave", isGuest: false }; -const LEGACY = { id: "0b9f6d1e-7c1a-4f7e-9a51-3f0c2b6d8e11", isGuest: true }; - -// Accepts tokens of the form "valid:". -const verifier: TokenVerifier = { - async verify(token: string): Promise { - if (!token.startsWith("valid:")) throw new InvalidTokenError("bad token"); - return { id: token.slice("valid:".length), isGuest: false }; - }, -}; - -describe("HTTP API", () => { - let rtmt: FakeRtmt; - let gameStore: GameStoreImpl; - let repository: InMemoryGameHistoryRepository; - let gameManager: GameManager; - let liveGames: LiveGames; - let server: http.Server; - let baseUrl: string; - - beforeEach(async () => { - rtmt = new FakeRtmt(); - gameStore = new GameStoreImpl(); - repository = new InMemoryGameHistoryRepository(); - const liveGameStore = new InMemoryLiveGameStore(); - const recorder = new GameRecorder(repository, liveGameStore); - const history = new GameHistoryService(repository, new InMemoryCache(), () => recorder.getUnsaved()); - recorder.onSaved((record) => history.onGameSaved(record)); - gameManager = new GameManager({ rtmt, gameStore, matchMaker: new MatchMakerImpl({ rtmt }), liveGameStore, recorder }); - liveGames = new LiveGames({ rtmt, gameStore, gameManager }); - const app = new ExpressApp({ - gameStore, gameManager, liveGames, history, tokenVerifier: verifier, legacyUserKeyAuth: true, accessLog: false, - }).app as http.RequestListener; - server = http.createServer(app); - await new Promise((resolve) => server.listen(0, resolve)); - baseUrl = `http://localhost:${(server.address() as AddressInfo).port}`; - [ALICE, BOB, CAROL, DAVE].forEach((user) => rtmt.connect(user)); - }); - - afterEach(async () => { - liveGames.dispose(); - await new Promise((resolve) => server.close(resolve)); - }); - - const get = async (path: string, userId?: string) => { - const response = await fetch(baseUrl + path, userId ? { headers: { Authorization: `Bearer valid:${userId}` } } : {}); - const body = response.headers.get("content-type")?.includes("json") ? await response.json() : undefined; - return { status: response.status, body }; - }; - - const startGame = (first: { id: string } = ALICE, second: { id: string } = BOB) => { - rtmt.receive(first.id, channel_new_game); - rtmt.receive(second.id, channel_new_game); - return gameStore.getGameByUser(first.id)!; - }; - - const finishGame = async (first: { id: string } = ALICE, second: { id: string } = BOB) => { - const game = startGame(first, second); - rtmt.receive(first.id, channel_leave_game); - await flushPromises(); - return repository.records.get(game.id)!; - }; - - describe("GET /game/:id", () => { - it("returns a live game as players receive it", async () => { - const game = startGame(); - rtmt.receive(CAROL.id, channel_listen_game_events, game.id); - const { status, body } = await get(`/game/${game.id}`); - expect(status).toBe(200); - expect(body.id).toBe(game.id); - expect(body.mancalaGame.player1Id).toBe(game.mancalaGame.player1Id); - expect(body.spectatorCount).toBe(1); - expect(body).not.toHaveProperty("spectatorIds"); - }); - - it("returns the saved record of a finished game", async () => { - const record = await finishGame(); - const { status, body } = await get(`/game/${record.id}`); - expect(status).toBe(200); - expect(body).toEqual({ status: "finished", record }); - }); - - it("answers 404 for unknown games and 503 when the database is down", async () => { - expect((await get("/game/unknown")).status).toBe(404); - repository.failing = true; - expect((await get("/game/unknown")).status).toBe(503); - }); - }); - - describe("GET /games/popular", () => { - it("lists live games, most spectated first, with public ids", async () => { - rtmt.connect(LEGACY); - const quiet = startGame(ALICE, LEGACY); - const watched = startGame(BOB, CAROL); - rtmt.receive(DAVE.id, channel_listen_game_events, watched.id); - - const { body } = await get("/games/popular?limit=5"); - expect(body.kind).toBe("live"); - expect(body.games.map((g: any) => g.id)).toEqual([watched.id, quiet.id]); - expect(body.games[0].spectatorCount).toBe(1); - expect(JSON.stringify(body)).not.toContain(LEGACY.id); - expect(JSON.stringify(body)).toContain(publicUserId(LEGACY.id)); - }); - - it("lists the most spectated finished games when nothing is live", async () => { - const watched = startGame(); - rtmt.receive(CAROL.id, channel_listen_game_events, watched.id); - rtmt.receive(ALICE.id, channel_leave_game); - const quiet = await finishGame(CAROL, DAVE); - await flushPromises(); - - const { body } = await get("/games/popular"); - expect(body.kind).toBe("finished"); - expect(body.games.map((g: any) => g.id)).toEqual([watched.id, quiet.id]); - expect(body.games[0]).not.toHaveProperty("moves"); - }); - - it("lists finished games while games are live when asked for finished games", async () => { - const finished = await finishGame(CAROL, DAVE); - await flushPromises(); - startGame(); - - expect((await get("/games/popular")).body.kind).toBe("live"); - const { body } = await get("/games/popular?kind=finished"); - expect(body.kind).toBe("finished"); - expect(body.games.map((g: any) => g.id)).toEqual([finished.id]); - }); - }); - - describe("GET /games/me/active", () => { - it("requires a valid token", async () => { - expect((await get("/games/me/active")).status).toBe(401); - const response = await fetch(baseUrl + "/games/me/active", { headers: { Authorization: "Bearer forged" } }); - expect(response.status).toBe(401); - }); - - it("returns the user's live game, or null", async () => { - expect((await get("/games/me/active", ALICE.id)).body).toEqual({ game: null }); - const game = startGame(); - const { body } = await get("/games/me/active", ALICE.id); - expect(body.game).toMatchObject({ id: game.id, turnPlayerId: game.mancalaGame.turnPlayerId, moveCount: 0 }); - expect(body.game.player1.name).toBeDefined(); - }); - }); - - describe("GET /games/me", () => { - it("requires a valid token", async () => { - expect((await get("/games/me")).status).toBe(401); - }); - - it("returns the user's latest finished games, and shows a new one right away despite the cache", async () => { - const first = await finishGame(ALICE, BOB); - await finishGame(CAROL, DAVE); - expect((await get("/games/me", ALICE.id)).body.games.map((g: GameRecord) => g.id)).toEqual([first.id]); - - const second = await finishGame(BOB, ALICE); - await flushPromises(); - const { body } = await get("/games/me?limit=1", ALICE.id); - expect(body.games.map((g: GameRecord) => g.id)).toEqual([second.id]); - }); - - it("returns an empty list while the database is down", async () => { - repository.failing = true; - expect((await get("/games/me", ALICE.id)).body).toEqual({ games: [] }); - }); - - it("includes a game that ended but is not saved yet", async () => { - repository.failing = true; - const game = startGame(); - rtmt.receive(ALICE.id, channel_leave_game); - await flushPromises(); - - expect((await get("/games/me", ALICE.id)).body.games.map((g: GameRecord) => g.id)).toEqual([game.id]); - expect((await get("/games/popular")).body.games.map((g: GameRecord) => g.id)).toEqual([game.id]); - const { body } = await get(`/game/${game.id}`); - expect(body.status).toBe("finished"); - expect(body.record.moves).toEqual([]); - }); - }); - - it("moves keep working with the API in place", async () => { - const game = startGame(); - rtmt.receive(game.mancalaGame.turnPlayerId, channel_game_move, { index: 0 }); - expect((await get(`/game/${game.id}`)).body.mancalaGame.history).toHaveLength(1); - }); -}); diff --git a/apps/backend/tests/support/NodeEnvironment.js b/apps/backend/tests/support/NodeEnvironment.js deleted file mode 100644 index e388e58..0000000 --- a/apps/backend/tests/support/NodeEnvironment.js +++ /dev/null @@ -1,19 +0,0 @@ -// Jest 27's node environment runs tests in a context that lacks Node's newer web globals (ReadableStream, Blob, -// ...), which the HTTP client used by testcontainers needs. Copy whatever the context is missing from Node. -const NodeEnvironment = require("jest-environment-node"); - -// Reading these makes Node warn about its experimental web storage. -const SKIPPED = new Set(["localStorage", "sessionStorage"]); - -class NodeEnvironmentWithWebGlobals extends NodeEnvironment { - constructor(config, context) { - super(config, context); - for (const name of Object.getOwnPropertyNames(globalThis)) { - if (!SKIPPED.has(name) && !(name in this.global)) { - this.global[name] = globalThis[name]; - } - } - } -} - -module.exports = NodeEnvironmentWithWebGlobals; diff --git a/apps/backend/tests/support/containers.ts b/apps/backend/tests/support/containers.ts deleted file mode 100644 index bc59972..0000000 --- a/apps/backend/tests/support/containers.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { GenericContainer, StartedTestContainer, Wait } from "testcontainers"; - -// Same images as apps/compose.yaml. -const POSTGRES_IMAGE = "postgres:17.6"; -const VALKEY_IMAGE = "valkey/valkey:9.1.2"; - -export interface RunningService { - url: string; - container: StartedTestContainer; -} - -export async function startPostgres(): Promise { - const container = await new GenericContainer(POSTGRES_IMAGE) - .withEnvironment({ POSTGRES_USER: "mancala", POSTGRES_PASSWORD: "mancala", POSTGRES_DB: "mancala" }) - .withExposedPorts(5432) - // Postgres logs this twice: once for the init run, once when it is really up. - .withWaitStrategy(Wait.forLogMessage(/database system is ready to accept connections/, 2)) - .start(); - return { - url: `postgres://mancala:mancala@${container.getHost()}:${container.getMappedPort(5432)}/mancala`, - container, - }; -} - -export async function startValkey(): Promise { - const container = await new GenericContainer(VALKEY_IMAGE) - .withExposedPorts(6379) - .withWaitStrategy(Wait.forLogMessage(/Ready to accept connections/)) - .start(); - return { url: `redis://${container.getHost()}:${container.getMappedPort(6379)}`, container }; -} diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json deleted file mode 100644 index cb46546..0000000 --- a/apps/backend/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "sourceMap": true - }, - "exclude": [ - "tests", - ] -} \ No newline at end of file diff --git a/apps/backend/yarn.lock b/apps/backend/yarn.lock deleted file mode 100644 index f8ab311..0000000 --- a/apps/backend/yarn.lock +++ /dev/null @@ -1,4805 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== - dependencies: - "@jridgewell/gen-mapping" "^0.1.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" - integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== - dependencies: - "@babel/highlight" "^7.16.7" - -"@babel/compat-data@^7.17.10": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab" - integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw== - -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.10.tgz#74ef0fbf56b7dfc3f198fc2d927f4f03e12f4b05" - integrity sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.10" - "@babel/helper-compilation-targets" "^7.17.10" - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helpers" "^7.17.9" - "@babel/parser" "^7.17.10" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.10" - "@babel/types" "^7.17.10" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - -"@babel/generator@^7.17.10", "@babel/generator@^7.7.2": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.10.tgz#c281fa35b0c349bbe9d02916f4ae08fc85ed7189" - integrity sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg== - dependencies: - "@babel/types" "^7.17.10" - "@jridgewell/gen-mapping" "^0.1.0" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.17.10": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz#09c63106d47af93cf31803db6bc49fef354e2ebe" - integrity sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ== - dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.20.2" - semver "^6.3.0" - -"@babel/helper-environment-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" - integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-function-name@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" - integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== - dependencies: - "@babel/template" "^7.16.7" - "@babel/types" "^7.17.0" - -"@babel/helper-hoist-variables@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" - integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-module-imports@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" - integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-module-transforms@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" - integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== - dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" - integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== - -"@babel/helper-simple-access@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" - integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== - dependencies: - "@babel/types" "^7.17.0" - -"@babel/helper-split-export-declaration@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" - integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== - -"@babel/helper-validator-option@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" - integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== - -"@babel/helpers@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a" - integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q== - dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.9" - "@babel/types" "^7.17.0" - -"@babel/highlight@^7.16.7": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3" - integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.10": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.10.tgz#873b16db82a8909e0fbd7f115772f4b739f6ce78" - integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ== - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.10.tgz#80031e6042cad6a95ed753f672ebd23c30933195" - integrity sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ== - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/template@^7.16.7", "@babel/template@^7.3.3": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" - integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/parser" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/traverse@^7.17.10", "@babel/traverse@^7.17.3", "@babel/traverse@^7.17.9", "@babel/traverse@^7.7.2": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.10.tgz#1ee1a5ac39f4eac844e6cf855b35520e5eb6f8b5" - integrity sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.10" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.10" - "@babel/types" "^7.17.10" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.17.10", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.10.tgz#d35d7b4467e439fcf06d195f8100e0fea7fc82c4" - integrity sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - to-fast-properties "^2.0.0" - -"@balena/dockerignore@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" - integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== - dependencies: - "@cspotcode/source-map-consumer" "0.8.0" - -"@grpc/grpc-js@^1.11.1": - version "1.14.6" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.14.6.tgz#f3f3d65315bf9b45252f575e4cc142148c587cac" - integrity sha512-SgC/B3/VZ8lTyOZ4Cwk6f1skWhmkNAaVfiX90qd5u1rRXGKk1//8FTUEN9GvOUq7rfWIqAYkA3CoyumYVpOS6g== - dependencies: - "@grpc/proto-loader" "^0.8.0" - "@js-sdsl/ordered-map" "^4.4.2" - -"@grpc/proto-loader@^0.7.13": - version "0.7.15" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz#4cdfbf35a35461fc843abe8b9e2c0770b5095e60" - integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ== - dependencies: - lodash.camelcase "^4.3.0" - long "^5.0.0" - protobufjs "^7.2.5" - yargs "^17.7.2" - -"@grpc/proto-loader@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.8.1.tgz#5a6b290ccbfb1ae2f6775afb74e9898bd8c5d4e8" - integrity sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg== - dependencies: - lodash.camelcase "^4.3.0" - long "^5.0.0" - protobufjs "^7.5.5" - yargs "^17.7.2" - -"@iovalkey/commands@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@iovalkey/commands/-/commands-0.1.0.tgz#62648b19106690ee8ce7d11aaf6249a242f927b0" - integrity sha512-/B9W4qKSSITDii5nkBCHyPkIkAi+ealUtr1oqBJsLxjSRLka4pxun2VvMNSmcwgAMxgXtQfl0qRv7TE+udPJzg== - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - 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" - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - 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" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" - integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== - dependencies: - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^27.5.1" - jest-util "^27.5.1" - slash "^3.0.0" - -"@jest/core@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" - integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== - dependencies: - "@jest/console" "^27.5.1" - "@jest/reporters" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.8.1" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^27.5.1" - jest-config "^27.5.1" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-resolve-dependencies "^27.5.1" - jest-runner "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - jest-watcher "^27.5.1" - micromatch "^4.0.4" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" - integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== - dependencies: - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - jest-mock "^27.5.1" - -"@jest/fake-timers@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" - integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== - dependencies: - "@jest/types" "^27.5.1" - "@sinonjs/fake-timers" "^8.0.1" - "@types/node" "*" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-util "^27.5.1" - -"@jest/globals@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" - integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/types" "^27.5.1" - expect "^27.5.1" - -"@jest/reporters@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" - integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-haste-map "^27.5.1" - jest-resolve "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" - slash "^3.0.0" - source-map "^0.6.0" - string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^8.1.0" - -"@jest/source-map@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" - integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== - dependencies: - callsites "^3.0.0" - graceful-fs "^4.2.9" - source-map "^0.6.0" - -"@jest/test-result@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" - integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== - dependencies: - "@jest/console" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" - integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== - dependencies: - "@jest/test-result" "^27.5.1" - graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-runtime "^27.5.1" - -"@jest/transform@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" - integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^27.5.1" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-regex-util "^27.5.1" - jest-util "^27.5.1" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - -"@jest/types@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" - integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== - -"@jridgewell/set-array@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" - integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.13" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" - integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== - -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.10" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.10.tgz#db436f0917d655393851bc258918c00226c9b183" - integrity sha512-Q0YbBd6OTsXm8Y21+YUSDXupHnodNC2M4O18jtd3iwJ3+vMZNdKGols0a9G6JOK0dcJ3IdUUHoh908ZI6qhk8Q== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@js-sdsl/ordered-map@^4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" - integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== - -"@kwsites/file-exists@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" - integrity sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw== - dependencies: - debug "^4.1.1" - -"@mancala/core@link:../../packages/core": - version "0.0.0" - uid "" - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.5.tgz#d9315ad7cf3f30aac70bda3c068443dc6f143659" - integrity sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g== - -"@protobufjs/eventemitter@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz#d512cb26c0ae026091ee2c1167f1be6faf5c842a" - integrity sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg== - -"@protobufjs/fetch@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.1.tgz#4d6fc00c8fb64016a5c81b469d549046350f1065" - integrity sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.2.tgz#78d476333d85d5b1c792e257bca74ba080da49a4" - integrity sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug== - -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^8.0.1": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" - integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== - dependencies: - "@sinonjs/commons" "^1.7.0" - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - -"@tsconfig/node10@^1.0.7": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" - integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== - -"@tsconfig/node12@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" - integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== - -"@tsconfig/node14@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== - -"@tsconfig/node16@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" - integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== - -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": - version "7.1.19" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" - integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.17.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" - integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== - dependencies: - "@babel/types" "^7.3.0" - -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/cors@^2.8.10": - version "2.8.12" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" - integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== - -"@types/docker-modem@*": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/docker-modem/-/docker-modem-3.0.6.tgz#1f9262fcf85425b158ca725699a03eb23cddbf87" - integrity sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg== - dependencies: - "@types/node" "*" - "@types/ssh2" "*" - -"@types/dockerode@^4.0.1": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-4.0.2.tgz#ced50a0b9c2633edd5a0c6e7b0a21537d38e427d" - integrity sha512-hA4myZ0XzwHG/p/eCLElYsQS3y1XZfvi6AfLFKVp1SNpPkO6J3xLatacAFVkReTxXRoIa3eHJmhJi9w6I3+zNA== - dependencies: - "@types/docker-modem" "*" - "@types/node" "*" - "@types/ssh2" "*" - -"@types/express-serve-static-core@^4.17.18": - version "4.17.28" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" - integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express@^4.17.12": - version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" - integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/graceful-fs@^4.1.2": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^26.0.23": - version "26.0.24" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" - integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== - dependencies: - jest-diff "^26.0.0" - pretty-format "^26.0.0" - -"@types/mime@^1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== - -"@types/morgan@^1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.3.tgz#ae04180dff02c437312bc0cfb1e2960086b2f540" - integrity sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q== - dependencies: - "@types/node" "*" - -"@types/node@*": - version "17.0.31" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.31.tgz#a5bb84ecfa27eec5e1c802c6bbf8139bdb163a5d" - integrity sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q== - -"@types/node@>=13.7.0": - version "26.6.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-26.6.5.tgz#cb35209e7767c503551b5fa42a48231c7441d979" - integrity sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg== - dependencies: - undici-types "~8.9.0" - -"@types/node@^15.12.4": - version "15.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" - integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== - -"@types/node@^18.11.18": - version "18.19.130" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.130.tgz#da4c6324793a79defb7a62cba3947ec5add00d59" - integrity sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg== - dependencies: - undici-types "~5.26.4" - -"@types/pg@8.23.1": - version "8.23.1" - resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.23.1.tgz#7e712ce02cf44ad100862127f10f52e9c6b9d227" - integrity sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A== - dependencies: - "@types/node" "*" - pg-protocol "*" - pg-types "^2.2.0" - -"@types/prettier@^2.1.5": - version "2.6.0" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.0.tgz#efcbd41937f9ae7434c714ab698604822d890759" - integrity sha512-G/AdOadiZhnJp0jXCaBQU449W2h716OW/EoXeYkCytxKL06X1WCXB4DZpp8TpZ8eyIJVS1cw4lrlkkSYU21cDw== - -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/serve-static@*": - version "1.13.10" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" - integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/ssh2-streams@*": - version "0.1.13" - resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.13.tgz#f8d34a22be50fb8dbafbb2bbc289add0d22daa51" - integrity sha512-faHyY3brO9oLEA0QlcO8N2wT7R0+1sHWZvQ+y3rMLwdY1ZyS1z0W3t65j9PqT4HmQ6ALzNe7RZlNuCNE0wBSWA== - dependencies: - "@types/node" "*" - -"@types/ssh2@*": - version "1.15.6" - resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-1.15.6.tgz#2481573c48c5d42e24d931e6e47d3a67d3d38162" - integrity sha512-oGdxhBqcRTwSTKFm+9EiKzkNVYRLEFkcW44lhguvBalGJbWfGnDt/ezwSUZc+SF9m9bMc3VyklNAtp7zICjS5w== - dependencies: - "@types/node" "^18.11.18" - -"@types/ssh2@^0.5.48": - version "0.5.52" - resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-0.5.52.tgz#9dbd8084e2a976e551d5e5e70b978ed8b5965741" - integrity sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg== - dependencies: - "@types/node" "*" - "@types/ssh2-streams" "*" - -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== - -"@types/uuid@^8.3.0": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" - integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== - -"@types/uuid@^9.0.8": - version "9.0.8" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" - integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== - -"@types/ws@^7.4.5": - version "7.4.7" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" - integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== - dependencies: - "@types/node" "*" - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^15.0.0": - version "15.0.14" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" - integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^16.0.0": - version "16.0.4" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" - integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== - dependencies: - "@types/yargs-parser" "*" - -abab@^2.0.3, abab@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-globals@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^7.1.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.2.4, acorn@^8.4.1: - version "8.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" - integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== - -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -ansi-align@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.0, ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.2.2: - version "6.4.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.4.0.tgz#69093d6436d30dbb9b04fe7bdcbcfe16c697d8a1" - integrity sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -ansi-styles@^6.1.0: - version "6.2.3" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" - integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== - -anymatch@^3.0.3, anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -archiver-utils@^5.0.0, archiver-utils@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d" - integrity sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA== - dependencies: - glob "^10.0.0" - graceful-fs "^4.2.0" - is-stream "^2.0.1" - lazystream "^1.0.0" - lodash "^4.17.15" - normalize-path "^3.0.0" - readable-stream "^4.0.0" - -archiver@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-7.0.1.tgz#c9d91c350362040b8927379c7aa69c0655122f61" - integrity sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ== - dependencies: - archiver-utils "^5.0.2" - async "^3.2.4" - buffer-crc32 "^1.0.0" - readable-stream "^4.0.0" - readdir-glob "^1.1.2" - tar-stream "^3.0.0" - zip-stream "^6.0.1" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -asn1@^0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - -async-lock@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.4.1.tgz#56b8718915a9b68b10fce2f2a9a3dddf765ef53f" - integrity sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ== - -async@^3.2.4: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -b4a@^1.6.4, b4a@^1.8.1, b4a@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.9.0.tgz#6336313b6e76b299c397afa68676ec6ebc788845" - integrity sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ== - -babel-jest@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" - integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== - dependencies: - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^27.5.1" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" - integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@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-top-level-await" "^7.8.3" - -babel-preset-jest@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" - integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== - dependencies: - babel-plugin-jest-hoist "^27.5.1" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -bare-events@^2.5.4, bare-events@^2.7.0: - version "2.9.2" - resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.9.2.tgz#01d377b64c3c7167b28b52da7add3221aecf2d7f" - integrity sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA== - -bare-fs@^4.0.1, bare-fs@^4.8.2: - version "4.8.4" - resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.8.4.tgz#f342288e7e5d9d8e79917078e1f45950507828c5" - integrity sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ== - dependencies: - bare-events "^2.5.4" - bare-path "^3.0.0" - bare-stream "^2.6.4" - bare-url "^2.2.2" - fast-fifo "^1.3.2" - -bare-path@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-3.1.2.tgz#67261bc74a9ba0105ba139b8638f218c09e8d82e" - integrity sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw== - -bare-stream@^2.6.4: - version "2.13.4" - resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.13.4.tgz#61d448a268d1efd992103aae6bd729332f278ac0" - integrity sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA== - dependencies: - b4a "^1.8.1" - streamx "^2.25.0" - teex "^1.0.1" - -bare-url@^2.2.2: - version "2.5.5" - resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.5.5.tgz#2e8c18db11393aa6cae6ff246fdcda34615aacd3" - integrity sha512-iM9qJCruvpt2rPaLsrumVDru9pogI7PHziHIa9kTNRFt8C8HuSa+kI9kFezK+NvHEFnnz71rKGZ1EAilq/3V/Q== - dependencies: - bare-path "^3.0.0" - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -basic-auth@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" - integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== - dependencies: - safe-buffer "5.1.2" - -bcrypt-pbkdf@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== - dependencies: - tweetnacl "^0.14.3" - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -body-parser@1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.10.3" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -boxen@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" - integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^6.2.0" - chalk "^4.1.0" - cli-boxes "^2.2.1" - string-width "^4.2.2" - type-fest "^0.20.2" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1, brace-expansion@^2.0.2: - version "2.1.7" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.7.tgz#14ea3836731823bb24740225ea43b941b436f9d1" - integrity sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - -browserslist@^4.20.2: - version "4.20.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf" - integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg== - dependencies: - caniuse-lite "^1.0.30001332" - electron-to-chromium "^1.4.118" - escalade "^3.1.1" - node-releases "^2.0.3" - picocolors "^1.0.0" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-crc32@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" - integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w== - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -buildcheck@~0.0.6: - version "0.0.7" - resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.7.tgz#07a5e76c10ead8fa67d9e4c587b68f49e8f29d61" - integrity sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA== - -byline@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" - integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -call-bind@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001332: - version "1.0.30001338" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001338.tgz#b5dd7a7941a51a16480bdf6ff82bded1628eec0d" - integrity sha512-1gLHWyfVoRDsHieO+CaeYe7jSo/MT7D7lhaXUiwwbuR5BwQxORs0f1tAwUSQr3YbxRXJvxHM/PA5FfPQRnsPeQ== - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -chokidar@^3.5.2: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -ci-info@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" - integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== - -cjs-module-lexer@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" - integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== - -cli-boxes@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - -cluster-key-slot@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" - integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -compress-commons@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-6.0.2.tgz#26d31251a66b9d6ba23a84064ecd3a6a71d2609e" - integrity sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg== - dependencies: - crc-32 "^1.2.0" - crc32-stream "^6.0.0" - is-stream "^2.0.1" - normalize-path "^3.0.0" - readable-stream "^4.0.0" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cpu-features@~0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5" - integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA== - dependencies: - buildcheck "~0.0.6" - nan "^2.19.0" - -crc-32@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" - integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== - -crc32-stream@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-6.0.0.tgz#8529a3868f8b27abb915f6c3617c0fadedbf9430" - integrity sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g== - dependencies: - crc-32 "^1.2.0" - readable-stream "^4.0.0" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -cssom@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== - -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== - dependencies: - cssom "~0.3.6" - -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== - dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.3.4, debug@^4.4.3: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -decimal.js@^10.2.1: - version "10.3.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" - integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deep-is@~0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -denque@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" - integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== - -depd@2.0.0, depd@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" - integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== - -diff-sequences@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" - integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -docker-compose@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-1.5.0.tgz#f46f6651ba08bdca3edc28f34dc6866c0e6c53ed" - integrity sha512-O+eD6c63+BJORUWddXA7uAlI6H1KLDPd/aS14k73nXYpFliM48C3xrrKB3sXsR1Fp/rHUZQe7+fJ+QA7+jepyw== - dependencies: - yaml "^2.2.2" - -docker-modem@^5.0.7: - version "5.0.7" - resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-5.0.7.tgz#57f3f0e2c7a893e66a0d4a626f9cbc933d77157b" - integrity sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA== - dependencies: - debug "^4.1.1" - readable-stream "^3.5.0" - split-ca "^1.0.1" - ssh2 "^1.15.0" - -dockerode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-5.0.1.tgz#c389c0e32e825a818a3964f8c9b26f60aa306be1" - integrity sha512-avsq/xk4YPIrn0CgleX5bjT9Y8IT1p9PxrNQ++RBQ2WEyFfHCTDsT9kmyxz+H/axnjAwg8wJWEIuPGOUuNupiA== - dependencies: - "@balena/dockerignore" "^1.0.2" - "@grpc/grpc-js" "^1.11.1" - "@grpc/proto-loader" "^0.7.13" - docker-modem "^5.0.7" - protobufjs "^7.3.2" - tar-fs "^2.1.4" - -domexception@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== - dependencies: - webidl-conversions "^5.0.0" - -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.4.118: - version "1.4.137" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz#186180a45617283f1c012284458510cd99d6787f" - integrity sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA== - -emittery@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" - integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -end-of-stream@^1.4.1: - version "1.4.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" - integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== - dependencies: - once "^1.4.0" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escodegen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" - integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -events-universal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/events-universal/-/events-universal-1.0.1.tgz#b56a84fd611b6610e0a2d0f09f80fdf931e2dfe6" - integrity sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw== - dependencies: - bare-events "^2.7.0" - -events@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - 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" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= - -expect@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" - integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== - dependencies: - "@jest/types" "^27.5.1" - jest-get-type "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - -express@^4.17.1: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.0" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.10.3" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fast-fifo@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" - integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== - dependencies: - bser "2.1.1" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -foreground-child@^3.1.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" - integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== - dependencies: - cross-spawn "^7.0.6" - signal-exit "^4.0.1" - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^2.3.2, fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-port@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" - integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== - -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^10.0.0: - version "10.5.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" - integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== - 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" - -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-dirs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" - integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== - dependencies: - ini "2.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.2, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -graceful-fs@^4.2.0, graceful-fs@^4.2.4: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -html-encoding-sniffer@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== - dependencies: - whatwg-encoding "^1.0.5" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.13, ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore-by-default@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= - -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -iovalkey@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/iovalkey/-/iovalkey-0.4.0.tgz#65316e7aabc980191f2122709569ec878b880d3b" - integrity sha512-OSUKxJ+s44CLdUTaicX4+pVrBN/zHKIYwr2oKhRDuczr19FwcSCXEZHzcMUYWZNh1mNSCV9bNJHW/T6cHVm9Aw== - dependencies: - "@iovalkey/commands" "^0.1.0" - cluster-key-slot "^1.1.0" - debug "^4.3.4" - denque "^2.1.0" - lodash.defaults "^4.2.0" - lodash.isarguments "^3.1.0" - redis-errors "^1.2.0" - redis-parser "^3.0.0" - standard-as-callback "^2.1.0" - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-core-module@^2.8.1: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== - dependencies: - has "^1.0.3" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-potential-custom-element-name@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" - integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== - -is-stream@^2.0.0, is-stream@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" - integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" - integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -jest-changed-files@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" - integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== - dependencies: - "@jest/types" "^27.5.1" - execa "^5.0.0" - throat "^6.0.1" - -jest-circus@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" - integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^0.7.0" - expect "^27.5.1" - is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - slash "^3.0.0" - stack-utils "^2.0.3" - throat "^6.0.1" - -jest-cli@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" - integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== - dependencies: - "@jest/core" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - import-local "^3.0.2" - jest-config "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - prompts "^2.0.1" - yargs "^16.2.0" - -jest-config@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" - integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== - dependencies: - "@babel/core" "^7.8.0" - "@jest/test-sequencer" "^27.5.1" - "@jest/types" "^27.5.1" - babel-jest "^27.5.1" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.1" - graceful-fs "^4.2.9" - jest-circus "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-get-type "^27.5.1" - jest-jasmine2 "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runner "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^27.5.1" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^26.0.0: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" - integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== - dependencies: - chalk "^4.0.0" - diff-sequences "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" - -jest-diff@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" - integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== - dependencies: - chalk "^4.0.0" - diff-sequences "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-docblock@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" - integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== - dependencies: - detect-newline "^3.0.0" - -jest-each@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" - integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== - dependencies: - "@jest/types" "^27.5.1" - chalk "^4.0.0" - jest-get-type "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - -jest-environment-jsdom@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" - integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" - jsdom "^16.6.0" - -jest-environment-node@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" - integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" - -jest-get-type@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" - integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== - -jest-get-type@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" - integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== - -jest-haste-map@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" - integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== - dependencies: - "@jest/types" "^27.5.1" - "@types/graceful-fs" "^4.1.2" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^27.5.1" - jest-serializer "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" - micromatch "^4.0.4" - walker "^1.0.7" - optionalDependencies: - fsevents "^2.3.2" - -jest-jasmine2@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" - integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - expect "^27.5.1" - is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - throat "^6.0.1" - -jest-leak-detector@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" - integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== - dependencies: - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-matcher-utils@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" - integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== - dependencies: - chalk "^4.0.0" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-message-util@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" - integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.5.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^27.5.1" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" - integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== - dependencies: - "@jest/types" "^27.5.1" - "@types/node" "*" - -jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== - -jest-regex-util@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" - integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== - -jest-resolve-dependencies@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" - integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== - dependencies: - "@jest/types" "^27.5.1" - jest-regex-util "^27.5.1" - jest-snapshot "^27.5.1" - -jest-resolve@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" - integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== - dependencies: - "@jest/types" "^27.5.1" - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-pnp-resolver "^1.2.2" - jest-util "^27.5.1" - jest-validate "^27.5.1" - resolve "^1.20.0" - resolve.exports "^1.1.0" - slash "^3.0.0" - -jest-runner@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" - integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== - dependencies: - "@jest/console" "^27.5.1" - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.8.1" - graceful-fs "^4.2.9" - jest-docblock "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-haste-map "^27.5.1" - jest-leak-detector "^27.5.1" - jest-message-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runtime "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" - source-map-support "^0.5.6" - throat "^6.0.1" - -jest-runtime@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" - integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/globals" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - execa "^5.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-serializer@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" - integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== - dependencies: - "@types/node" "*" - graceful-fs "^4.2.9" - -jest-snapshot@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" - integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== - dependencies: - "@babel/core" "^7.7.2" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" - "@babel/types" "^7.0.0" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.1.5" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^27.5.1" - graceful-fs "^4.2.9" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - jest-haste-map "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-util "^27.5.1" - natural-compare "^1.4.0" - pretty-format "^27.5.1" - semver "^7.3.2" - -jest-util@^27.0.0, jest-util@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" - integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== - dependencies: - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" - integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== - dependencies: - "@jest/types" "^27.5.1" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^27.5.1" - leven "^3.1.0" - pretty-format "^27.5.1" - -jest-watcher@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" - integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== - dependencies: - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - jest-util "^27.5.1" - string-length "^4.0.1" - -jest-worker@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^27.0.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" - integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== - dependencies: - "@jest/core" "^27.5.1" - import-local "^3.0.2" - jest-cli "^27.5.1" - -jose@5.10.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/jose/-/jose-5.10.0.tgz#c37346a099d6467c401351a9a0c2161e0f52c4be" - integrity sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsdom@^16.6.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== - dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" - escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" - symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json5@2.x, json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -latest-version@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - -lazystream@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" - integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== - dependencies: - readable-stream "^2.0.5" - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== - -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== - -lodash.isarguments@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== - -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - -lodash@^4.17.15: - version "4.18.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" - integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== - -lodash@^4.7.0: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -long@^5.0.0, long@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" - integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-error@1.x, make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -"mancala.js@link:../../packages/mancala.js": - version "0.0.0" - uid "" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.1.0: - version "5.1.9" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.9.tgz#1293ef15db0098b394540e8f9f744f9fda8dee4b" - integrity sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.4: - version "9.0.9" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" - integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== - dependencies: - brace-expansion "^2.0.2" - -minimist@^1.2.0: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: - version "7.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" - integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== - -mkdirp-classic@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - -mkdirp@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" - integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== - -morgan@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" - integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ== - dependencies: - basic-auth "~2.0.1" - debug "2.6.9" - depd "~2.0.0" - on-finished "~2.3.0" - on-headers "~1.0.2" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3, ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nan@^2.19.0, nan@^2.23.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.29.0.tgz#95bace50f153289240573c08401acc63a30659d4" - integrity sha512-GlGk3HIvitbvs+LT3g6XUP1kpirKNvmDFwF/bmo6XNWSb/eYEs/O4bfgIEIXCZ+lIOTS5xNwDvSGMw6FJdAhtA== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= - -node-releases@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476" - integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ== - -nodemon@^2.0.7: - version "2.0.16" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.16.tgz#d71b31bfdb226c25de34afea53486c8ef225fdef" - integrity sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w== - dependencies: - chokidar "^3.5.2" - debug "^3.2.7" - ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.8" - semver "^5.7.1" - supports-color "^5.5.0" - touch "^3.1.0" - undefsafe "^2.0.5" - update-notifier "^5.1.0" - -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= - dependencies: - abbrev "1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nwsapi@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" - integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-inspect@^1.9.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" - integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - 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" - -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -pg-cloudflare@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz#0fc904c9a8b3ca3a4f71e16bcc9171224ecbb2a8" - integrity sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ== - -pg-connection-string@^2.14.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.14.1.tgz#d9529fe2497c0cac8a9f5f6f059679c52f1bd964" - integrity sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA== - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" - integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== - -pg-pool@^3.14.0: - version "3.14.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.14.0.tgz#f35ae4eb846780cad71af24099b3edfa9781ad90" - integrity sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw== - -pg-protocol@*, pg-protocol@^1.16.1: - version "1.16.1" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.16.1.tgz#87a3951b57002682d63166c029859bd834e64bf1" - integrity sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg== - -pg-types@2.2.0, pg-types@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" - integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== - dependencies: - pg-int8 "1.0.1" - postgres-array "~2.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.4" - postgres-interval "^1.1.0" - -pg@8.23.1: - version "8.23.1" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.23.1.tgz#e4848051a04aa2f968fd5671d075f89e5e7918b9" - integrity sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ== - dependencies: - pg-connection-string "^2.14.1" - pg-pool "^3.14.0" - pg-protocol "^1.16.1" - pg-types "2.2.0" - pgpass "1.0.5" - optionalDependencies: - pg-cloudflare "^1.4.1" - -pgpass@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" - integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== - dependencies: - split2 "^4.1.0" - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pirates@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -postgres-array@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" - integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== - -postgres-bytea@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.1.tgz#c40b3da0222c500ff1e51c5d7014b60b79697c7a" - integrity sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ== - -postgres-date@~1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" - integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== - -postgres-interval@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" - integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== - dependencies: - xtend "^4.0.0" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - -pretty-format@^26.0.0, pretty-format@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" - integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== - dependencies: - "@jest/types" "^26.6.2" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^17.0.1" - -pretty-format@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== - dependencies: - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^17.0.1" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -proper-lockfile@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" - integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== - dependencies: - graceful-fs "^4.2.4" - retry "^0.12.0" - signal-exit "^3.0.2" - -properties-reader@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/properties-reader/-/properties-reader-3.0.1.tgz#576af69708759bb75672bfc162b80cc8a3d1bdb2" - integrity sha512-WPn+h9RGEExOKdu4bsF4HksG/uzd3cFq3MFtq8PsFeExPse5Ha/VOjQNyHhjboBFwGXGev6muJYTSPAOkROq2g== - dependencies: - "@kwsites/file-exists" "^1.1.1" - mkdirp "^3.0.1" - -protobufjs@^7.2.5, protobufjs@^7.3.2, protobufjs@^7.5.5: - version "7.6.6" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.6.6.tgz#7a3923e8e32b0ee2ff689f8eed6e455c090ac67e" - integrity sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.5" - "@protobufjs/eventemitter" "^1.1.1" - "@protobufjs/fetch" "^1.1.1" - "@protobufjs/float" "^1.0.2" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.1" - "@types/node" ">=13.7.0" - long "^5.3.2" - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -psl@^1.1.33: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== - -pstree.remy@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" - integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - -readable-stream@^2.0.5: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^4.0.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.8.0.tgz#22af894e6d60cb0064410fc376367ec4ce470adb" - integrity sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ== - dependencies: - abort-controller "^3.0.0" - buffer "^6.0.3" - events "^3.3.0" - process "^0.11.10" - string_decoder "^1.3.0" - -readdir-glob@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" - integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== - dependencies: - minimatch "^5.1.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -redis-errors@^1.0.0, redis-errors@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== - -redis-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== - dependencies: - redis-errors "^1.0.0" - -registry-auth-token@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" - integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== - dependencies: - rc "^1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== - -resolve@^1.20.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" - integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== - dependencies: - is-core-module "^2.8.1" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== - -rimraf@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== - dependencies: - xmlchars "^2.2.0" - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver@7.x, semver@^7.3.2, semver@^7.3.4: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== - dependencies: - lru-cache "^6.0.0" - -semver@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-support@^0.5.6: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - -split-ca@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" - integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ== - -split2@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" - integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -ssh-remote-port-forward@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz#72b0c5df8ec27ca300c75805cc6b266dee07e298" - integrity sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ== - dependencies: - "@types/ssh2" "^0.5.48" - ssh2 "^1.4.0" - -ssh2@^1.15.0, ssh2@^1.4.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.17.0.tgz#dc686e8e3abdbd4ad95d46fa139615903c12258c" - integrity sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ== - dependencies: - asn1 "^0.2.6" - bcrypt-pbkdf "^1.0.2" - optionalDependencies: - cpu-features "~0.0.10" - nan "^2.23.0" - -stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== - dependencies: - escape-string-regexp "^2.0.0" - -standard-as-callback@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" - integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -streamx@^2.12.5, streamx@^2.25.0, streamx@^2.28.1: - version "2.28.1" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.28.1.tgz#376cd42a089505a69bec0efdb5189aa45586141f" - integrity sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA== - dependencies: - events-universal "^1.0.0" - fast-fifo "^1.3.2" - text-decoder "^1.1.0" - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string_decoder@^1.1.1, string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3" - integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== - dependencies: - ansi-regex "^6.2.2" - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -supports-color@^5.3.0, supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-hyperlinks@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -symbol-tree@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" - integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== - -tar-fs@^2.1.4: - version "2.1.5" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.5.tgz#33e9c29413dce0c58ada7ff77db4e5a30afffe70" - integrity sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-fs@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.1.3.tgz#05668cc68a30741c3813f9c16593b8dec7dcbcd1" - integrity sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ== - dependencies: - pump "^3.0.0" - tar-stream "^3.1.5" - optionalDependencies: - bare-fs "^4.0.1" - bare-path "^3.0.0" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar-stream@^3.0.0, tar-stream@^3.1.5: - version "3.2.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.2.2.tgz#aec7f257e712192a177a93aa0450c2e76855c8be" - integrity sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg== - dependencies: - b4a "^1.9.0" - bare-fs "^4.8.2" - fast-fifo "^1.3.2" - streamx "^2.28.1" - -teex@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/teex/-/teex-1.0.1.tgz#b8fa7245ef8e8effa8078281946c85ab780a0b12" - integrity sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg== - dependencies: - streamx "^2.12.5" - -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -testcontainers@12.2.0: - version "12.2.0" - resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-12.2.0.tgz#d2e99b8895d1283f7a2b629ca14b1ac9b65cc2e2" - integrity sha512-Pd0c2eU0DdIuK+Pb3c+vmBUdKjDLgxZmPecQGESFAKyn5LTqSaSw8l7htfMlLwabb/QsUILe6XkxMJw8Ly/ThA== - dependencies: - "@balena/dockerignore" "^1.0.2" - "@types/dockerode" "^4.0.1" - archiver "^7.0.1" - async-lock "^1.4.1" - byline "^5.0.0" - debug "^4.4.3" - docker-compose "^1.5.0" - dockerode "^5.0.1" - get-port "^5.1.1" - proper-lockfile "^4.1.2" - properties-reader "^3.0.1" - ssh-remote-port-forward "^1.0.4" - tar-fs "^3.1.3" - tmp "^0.2.7" - undici "^8.10.2" - -text-decoder@^1.1.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.7.tgz#5d073a9a74b9c0a9d28dfadcab96b604af57d8ba" - integrity sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ== - dependencies: - b4a "^1.6.4" - -throat@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" - integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== - -tiny-emitter@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" - integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== - -tmp@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.7.tgz#26f4db11d1601ce8012dcb8a798ece1c06a99059" - integrity sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw== - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -touch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== - dependencies: - nopt "~1.0.10" - -tough-cookie@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" - integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.1.2" - -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== - dependencies: - punycode "^2.1.1" - -ts-jest@^27.0.3: - version "27.1.4" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.4.tgz#84d42cf0f4e7157a52e7c64b1492c46330943e00" - integrity sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ== - dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^27.0.0" - json5 "2.x" - lodash.memoize "4.x" - make-error "1.x" - semver "7.x" - yargs-parser "20.x" - -ts-node@^10.0.0: - version "10.7.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" - integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== - dependencies: - "@cspotcode/source-map-support" "0.7.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.0" - yn "3.1.1" - -tweetnacl@^0.14.3: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typescript@^4.7.4: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== - -undefsafe@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" - integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -undici-types@~8.9.0: - version "8.9.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.9.0.tgz#e240d97c8b5d85e5347ce73d25865c7906c1ec9f" - integrity sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg== - -undici@^8.10.2: - version "8.12.2" - resolved "https://registry.yarnpkg.com/undici/-/undici-8.12.2.tgz#e869237430adcc5de6bca798c94edd7db9dac5ac" - integrity sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg== - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -universalify@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -update-notifier@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -uuid@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" - integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-compile-cache-lib@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -v8-to-istanbul@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" - integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== - dependencies: - xml-name-validator "^3.0.0" - -walker@^1.0.7: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== - -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" - -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== - -whatwg-url@^8.0.0, whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== - dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -ws@^7.4.6, ws@^7.5.0: - version "7.5.7" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" - integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== - -xmlchars@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^2.2.2: - version "2.9.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.1.tgz#c16233fb31944e705cfefaff38795587f57588ce" - integrity sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw== - -yargs-parser@20.x, yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@^17.7.2: - version "17.7.3" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.3.tgz#779dffe6bcafec596a7172e983289a588647faaa" - integrity sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g== - 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" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -zip-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-6.0.1.tgz#e141b930ed60ccaf5d7fa9c8260e0d1748a2bbfb" - integrity sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA== - dependencies: - archiver-utils "^5.0.0" - compress-commons "^6.0.2" - readable-stream "^4.0.0" diff --git a/apps/compose.yaml b/apps/compose.yaml index f9fbc60..4f5e078 100644 --- a/apps/compose.yaml +++ b/apps/compose.yaml @@ -11,7 +11,9 @@ services: KEYCLOAK_JWKS_URL: http://keycloak:8080/realms/mancala/protocol/openid-connect/certs KEYCLOAK_CLIENT_IDS: mancala-web LEGACY_USERKEY_AUTH: ${LEGACY_USERKEY_AUTH:-true} - DATABASE_URL: postgres://mancala:${MANCALA_DB_PASSWORD}@postgres:5432/mancala + DATABASE_JDBC_URL: jdbc:postgresql://postgres:5432/mancala + DATABASE_USERNAME: mancala + DATABASE_PASSWORD: ${MANCALA_DB_PASSWORD} VALKEY_URL: redis://valkey:6379 ports: - "5000:6000" diff --git a/docs/specs/quarkus-backend/tasks.md b/docs/specs/quarkus-backend/tasks.md index d30c6a5..fcce883 100644 --- a/docs/specs/quarkus-backend/tasks.md +++ b/docs/specs/quarkus-backend/tasks.md @@ -14,7 +14,7 @@ Work through the tasks in order; each ends with its tests passing and a commit. `ValkeyLiveGameStore`, `ValkeyCache`; integration tests with Dev Services. - [x] 6. **WebSocket and HTTP**: `RtmtServer` with unit tests, `RtmtEndpoint`, the HTTP resources, `Backend` wiring and startup, configuration; `@QuarkusTest`s for the API and the WebSocket. -- [ ] 7. **Replace the Node backend**: move the project to `apps/backend`, Dockerfile, `.dockerignore`, compose, +- [x] 7. **Replace the Node backend**: move the project to `apps/backend`, Dockerfile, `.dockerignore`, compose, remove the Node sources; build the image. - [ ] 8. **End to end and docs**: the Chrome tests from `design.md` against the local stack; `README.md`, `apps/backend/README.md`, `AGENTS.md`, `TODO.md`.