diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ad2e0c5..de2adbf 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -5,6 +5,7 @@ on: branches: [main] pull_request: branches: [main] + workflow_dispatch: jobs: test: @@ -46,4 +47,4 @@ jobs: TMT_COMMIT_SHA=${{ github.sha }} tags: | jensforstmann/tmt2:${{ github.sha }} - jensforstmann/tmt2:latest + jensforstmann/tmt2:dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ce9031..52582fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,3 +36,4 @@ jobs: jensforstmann/tmt2:${{ steps.tagName.outputs.major }} jensforstmann/tmt2:${{ steps.tagName.outputs.major }}.${{ steps.tagName.outputs.minor }} jensforstmann/tmt2:${{ steps.tagName.outputs.major }}.${{ steps.tagName.outputs.minor }}.${{ steps.tagName.outputs.patch }} + jensforstmann/tmt2:latest diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b60db50 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,121 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Add `.admin` ingame command which sets the new `needsAttentionSince` property on the match data. +- Add predefined match list filter to frontend to show all live matches which needs attention. +- Add notification bell to frontend to indicate how many matches need attention. + +### Changed + +- **BREAKING:** Remove `logs` field from match data API responses. It was never used but an empty array instead. +- **BREAKING:** Remove HTTP `logs` endpoint for a specific match in favor of the `events` endpoint (which also contains logs). +- **BREAKING:** All optional fields in API responses are now always present and might be `null` instead of being not there at all. +- Move some navbar buttons to a new settings menu in the frontend: Login/logout, theme selection and hyperlink to info page. +- Improve WebSocket connection between browser and TMT. +- Some data is now saved in a SQLite database: `match`, `matchMap`, `matchPlayer`, `event`, `managedGameServer`. Existing data will be migrated from JSON files. + +### Fixed + +- Fix match map being in wrong (`PAUSED`) state when ingame vote for tactical timeout (or the `.tac` command) is used together with TMT's `.pause` command. + +## [2.9.1] - 2024-10-08 + +### Fixed + +- Re-add previous docker tag schema with leading "v" for backwards compatibility (e.g. v2). + +## [2.9.0] - 2024-10-08 + +### Added + +- Make presets usable for logged in or all users (configurable per preset). +- Update 2on2 wingman default preset (active duty map pool & election steps). +- Add docker compose file. +- Add `MATCH_STOP` webhook (sent when TMT stops supervising a match). +- Improve (rcon and log) connections between game server and TMT, especially when game server has crashed/changed. +- Add workaround for CS2 getting stuck after loading round backup. + +### Changed + +- Tag docker container with a correct semver version (2.9.0, 2.9, 2 instead of v2.8, v2) + +### Fixed + +- Fix copy to clipboard not always working. +- Fix redirect from edit match page back to match page. + +## [2.8.0] - 2024-09-20 + +### Added + +- Add `.tac` command for tactical timeouts (will send `timeout_ct_start`/`timeout_terrorist_start` commands to the CS2 server). +- Improve team joining process (`.team a`/`.team b`): send various ingame chat message to help the players to pick the right team. +- Add support for workshop maps. +- Dynamic map change delay: wait for `mp_match_restart_delay` seconds before loading the next map (to not cut off casters/specatators on the CSTV server). +- Custom headers can be added to all webhook requests (e.g. for auth). +- Add separate page to send rcon commands to managed game servers (independent of a match). +- Improve detection and handling of dangling matches. + A dangling match is currently not being supervised (not tracked by TMT) and has not been stopped properly. + A match must be either stopped via the UI ("stop") or the API (`DELETE`). + This can happen if the game server goes offline and TMT quits. + Next time TMT starts it tries to resume unfinished matches, if the match cannot be continued (game server is still offline) the match is dangling. + +## [2.7.0] - 2023-11-15 + +### Added + +- Track online state for each player. +- Make displayed columns in match list configurable. + +### Fixed + +- Fix copy to clipboard not working in some cases + +## [2.6.0] - 2023-10-20 + +### Added + +- Add color support for chat messages. +- Track team sides for each player (CT/T) and display them in the frontend. +- Add preset system to save and reuse match creation payload data. + +## [2.5.0] - 2023-10-09 + +### Fixed + +- Fix that the game server does not pause after loading a round backup despite `mp_backup_restore_load_autopause = true`. + +## [2.4.0] - 2023-10-08 + +### Added + +- Add support for Counter-Strike 2. + +## [2.3.0] - 2023-04-21 + +### Added + +- Game servers managed by TMT: If game server property is `null` when match is created a game server managed by TMT will be assigned. +- Loop mode: If enabled (for a match) and after the match has ended or if there are no players left on the server, the match will restart from the beginning (starting with the election process). +- Add pages to frontend to create and update matches. + +## [2.2.0] - 2022-08-27 + +### Added + +- Add method to switch the internal team assignments (in case the teams are already playing, but are in the wrong teams). +- Print auto generated access token to console. +- Live sync match state via WebSocket to the frontend. +- Add dark mode to the frontend. + +### Changed + +- Environment variable `TMT_LOG_ADDRESS` is now optional, if omitted one must be set in match creation payload (`tmtLogAddress`). diff --git a/backend/package-lock.json b/backend/package-lock.json index 8ab5432..2ffafa7 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,37 +1,37 @@ { "name": "tmt2-backend", - "version": "2.0.0", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tmt2-backend", - "version": "2.0.0", + "version": "3.0.0", "license": "MIT", "dependencies": { "@tsoa/runtime": "^6.6.0", - "express": "^5.1.0", - "short-uuid": "^5.2.0", + "better-sqlite3": "^12.9.0", + "express": "^5.2.1", + "short-uuid": "^6.0.3", "steamid": "^2.1.0", "typed-emitter": "^2.1.0", - "ws": "^8.18.3" + "ws": "^8.20.0" }, "devDependencies": { "@tsoa/cli": "^6.6.0", - "@types/debug": "^4.1.12", - "@types/express": "^5.0.5", - "@types/node": "^24.10.0", + "@types/better-sqlite3": "^7.6.13", + "@types/debug": "^4.1.13", + "@types/express": "^5.0.6", + "@types/node": "^25.6.0", "@types/steamid": "^2.0.4", "@types/ws": "^8.18.1", - "nodemon": "^3.1.10", + "nodemon": "^3.1.14", "ts-node": "^10.9.2", - "typescript": "^5.9.3" + "typescript": "^6.0.2" } }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", "dependencies": { @@ -43,8 +43,6 @@ }, "node_modules/@hapi/accept": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.3.tgz", - "integrity": "sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -53,8 +51,6 @@ }, "node_modules/@hapi/ammo": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-6.0.1.tgz", - "integrity": "sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2" @@ -62,8 +58,6 @@ }, "node_modules/@hapi/b64": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz", - "integrity": "sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2" @@ -71,8 +65,6 @@ }, "node_modules/@hapi/boom": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2" @@ -80,8 +72,6 @@ }, "node_modules/@hapi/bounce": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.2.tgz", - "integrity": "sha512-d0XmlTi3H9HFDHhQLjg4F4auL1EY3Wqj7j7/hGDhFFe6xAbnm3qiGrXeT93zZnPH8gH+SKAFYiRzu26xkXcH3g==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -90,14 +80,10 @@ }, "node_modules/@hapi/bourne": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", - "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", "license": "BSD-3-Clause" }, "node_modules/@hapi/call": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@hapi/call/-/call-9.0.1.tgz", - "integrity": "sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -106,8 +92,6 @@ }, "node_modules/@hapi/catbox": { "version": "12.1.1", - "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.1.tgz", - "integrity": "sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -118,8 +102,6 @@ }, "node_modules/@hapi/catbox-memory": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-6.0.2.tgz", - "integrity": "sha512-H1l4ugoFW/ZRkqeFrIo8p1rWN0PA4MDTfu4JmcoNDvnY975o29mqoZblqFTotxNHlEkMPpIiIBJTV+Mbi+aF0g==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -128,8 +110,6 @@ }, "node_modules/@hapi/content": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.0.tgz", - "integrity": "sha512-CEhs7j+H0iQffKfe5Htdak5LBOz/Qc8TRh51cF+BFv0qnuph3Em4pjGVzJMkI2gfTDdlJKWJISGWS1rK34POGA==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.0" @@ -137,8 +117,6 @@ }, "node_modules/@hapi/cryptiles": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.1.tgz", - "integrity": "sha512-9GM9ECEHfR8lk5ASOKG4+4ZsEzFqLfhiryIJ2ISePVB92OHLp/yne4m+zn7z9dgvM98TLpiFebjDFQ0UHcqxXQ==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1" @@ -149,14 +127,10 @@ }, "node_modules/@hapi/file": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@hapi/file/-/file-3.0.0.tgz", - "integrity": "sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q==", "license": "BSD-3-Clause" }, "node_modules/@hapi/hapi": { "version": "21.4.3", - "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.4.3.tgz", - "integrity": "sha512-Q7g0ZY4gxU69wabFKH75qR0AFOdiOECj6vGqTHBSO5Lrwe6TwD8r9LkYQIbvtG8N423VDpdVsiZP8MnBwmD6Hw==", "license": "BSD-3-Clause", "dependencies": { "@hapi/accept": "^6.0.3", @@ -184,8 +158,6 @@ }, "node_modules/@hapi/heavy": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-8.0.1.tgz", - "integrity": "sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -195,14 +167,10 @@ }, "node_modules/@hapi/hoek": { "version": "11.0.7", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", - "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", "license": "BSD-3-Clause" }, "node_modules/@hapi/iron": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-7.0.1.tgz", - "integrity": "sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ==", "license": "BSD-3-Clause", "dependencies": { "@hapi/b64": "^6.0.1", @@ -214,8 +182,6 @@ }, "node_modules/@hapi/mimos": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-7.0.1.tgz", - "integrity": "sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2", @@ -224,8 +190,6 @@ }, "node_modules/@hapi/nigel": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-5.0.1.tgz", - "integrity": "sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2", @@ -237,8 +201,6 @@ }, "node_modules/@hapi/pez": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-6.1.0.tgz", - "integrity": "sha512-+FE3sFPYuXCpuVeHQ/Qag1b45clR2o54QoonE/gKHv9gukxQ8oJJZPR7o3/ydDTK6racnCJXxOyT1T93FCJMIg==", "license": "BSD-3-Clause", "dependencies": { "@hapi/b64": "^6.0.1", @@ -250,8 +212,6 @@ }, "node_modules/@hapi/podium": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.2.tgz", - "integrity": "sha512-T7gf2JYHQQfEfewTQFbsaXoZxSvuXO/QBIGljucUQ/lmPnTTNAepoIKOakWNVWvo2fMEDjycu77r8k6dhreqHA==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2", @@ -261,8 +221,6 @@ }, "node_modules/@hapi/shot": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-6.0.2.tgz", - "integrity": "sha512-WKK1ShfJTrL1oXC0skoIZQYzvLsyMDEF8lfcWuQBjpjCN29qivr9U36ld1z0nt6edvzv28etNMOqUF4klnHryw==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2", @@ -271,8 +229,6 @@ }, "node_modules/@hapi/somever": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-4.1.1.tgz", - "integrity": "sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg==", "license": "BSD-3-Clause", "dependencies": { "@hapi/bounce": "^3.0.1", @@ -281,8 +237,6 @@ }, "node_modules/@hapi/statehood": { "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-8.2.0.tgz", - "integrity": "sha512-63JlCVIrsmuunWsyc3OeuFO+gH6v56swLCl7OM1w09l/exQKPUxSUDF2Slkuw8k91nIzr0A2/aPvjLOWf9ksrg==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -296,8 +250,6 @@ }, "node_modules/@hapi/subtext": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-8.1.1.tgz", - "integrity": "sha512-ex1Y2s/KuJktS8Ww0k6XJ5ysSKrzNym4i5pDVuCwlSgHHviHUsT1JNzE6FYhNU9TTHSNdyfue/t2m89bpkX9Jw==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -311,8 +263,6 @@ }, "node_modules/@hapi/teamwork": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-6.0.1.tgz", - "integrity": "sha512-52OXRslUfYwXAOG8k58f2h2ngXYQGP0x5RPOo+eWA/FtyLgHjGMrE3+e9LSXP/0q2YfHAK5wj9aA9DTy1K+kyQ==", "license": "BSD-3-Clause", "engines": { "node": ">=14.0.0" @@ -320,8 +270,6 @@ }, "node_modules/@hapi/topo": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2" @@ -329,8 +277,6 @@ }, "node_modules/@hapi/validate": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", - "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2", @@ -339,8 +285,6 @@ }, "node_modules/@hapi/vise": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-5.0.1.tgz", - "integrity": "sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2" @@ -348,8 +292,6 @@ }, "node_modules/@hapi/wreck": { "version": "18.1.0", - "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.0.tgz", - "integrity": "sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -359,8 +301,6 @@ }, "node_modules/@isaacs/cliui": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", "dependencies": { @@ -377,8 +317,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -387,15 +325,11 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -405,8 +339,6 @@ }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, @@ -416,36 +348,26 @@ }, "node_modules/@tsconfig/node10": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true, "license": "MIT" }, "node_modules/@tsoa/cli": { "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@tsoa/cli/-/cli-6.6.0.tgz", - "integrity": "sha512-thSW0EiqjkF7HspcPIVIy0ZX65VqbWALHbxwl8Sk83j2kakOMq+fJvfo8FcBAWlMki+JDH7CO5iaAaSLHbeqtg==", "dev": true, "license": "MIT", "dependencies": { @@ -470,10 +392,20 @@ "yarn": ">=1.9.4" } }, + "node_modules/@tsoa/cli/node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@tsoa/runtime": { "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@tsoa/runtime/-/runtime-6.6.0.tgz", - "integrity": "sha512-+rF2gdL8CX+jQ82/IBc+MRJFNAvWPoBBl77HHJv3ESVMqbKhlhlo97JHmKyFbLcX6XOJN8zl8gfQpAEJN4SOMQ==", "license": "MIT", "dependencies": { "@hapi/boom": "^10.0.1", @@ -491,8 +423,6 @@ }, "node_modules/@tsoa/runtime/node_modules/accepts": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -504,8 +434,6 @@ }, "node_modules/@tsoa/runtime/node_modules/body-parser": { "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -528,8 +456,6 @@ }, "node_modules/@tsoa/runtime/node_modules/content-disposition": { "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -540,8 +466,6 @@ }, "node_modules/@tsoa/runtime/node_modules/cookie": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -549,14 +473,10 @@ }, "node_modules/@tsoa/runtime/node_modules/cookie-signature": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, "node_modules/@tsoa/runtime/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -564,14 +484,10 @@ }, "node_modules/@tsoa/runtime/node_modules/debug/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/@tsoa/runtime/node_modules/express": { "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -616,8 +532,6 @@ }, "node_modules/@tsoa/runtime/node_modules/finalhandler": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -634,8 +548,6 @@ }, "node_modules/@tsoa/runtime/node_modules/fresh": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -643,8 +555,6 @@ }, "node_modules/@tsoa/runtime/node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -655,8 +565,6 @@ }, "node_modules/@tsoa/runtime/node_modules/media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -664,8 +572,6 @@ }, "node_modules/@tsoa/runtime/node_modules/merge-descriptors": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -673,8 +579,6 @@ }, "node_modules/@tsoa/runtime/node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -682,8 +586,6 @@ }, "node_modules/@tsoa/runtime/node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -694,8 +596,6 @@ }, "node_modules/@tsoa/runtime/node_modules/negotiator": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -703,14 +603,10 @@ }, "node_modules/@tsoa/runtime/node_modules/path-to-regexp": { "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/@tsoa/runtime/node_modules/qs": { "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -724,8 +620,6 @@ }, "node_modules/@tsoa/runtime/node_modules/raw-body": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -739,8 +633,6 @@ }, "node_modules/@tsoa/runtime/node_modules/send": { "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -763,8 +655,6 @@ }, "node_modules/@tsoa/runtime/node_modules/send/node_modules/encodeurl": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -772,8 +662,6 @@ }, "node_modules/@tsoa/runtime/node_modules/serve-static": { "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", @@ -787,8 +675,6 @@ }, "node_modules/@tsoa/runtime/node_modules/statuses": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -796,8 +682,6 @@ }, "node_modules/@tsoa/runtime/node_modules/type-is": { "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -809,8 +693,14 @@ }, "node_modules/@types/accepts": { "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -818,8 +708,6 @@ }, "node_modules/@types/body-parser": { "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "license": "MIT", "dependencies": { "@types/connect": "*", @@ -828,8 +716,6 @@ }, "node_modules/@types/connect": { "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -837,14 +723,10 @@ }, "node_modules/@types/content-disposition": { "version": "0.5.9", - "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz", - "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==", "license": "MIT" }, "node_modules/@types/cookies": { "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.2.tgz", - "integrity": "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A==", "license": "MIT", "dependencies": { "@types/connect": "*", @@ -854,9 +736,7 @@ } }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", "dev": true, "license": "MIT", "dependencies": { @@ -864,20 +744,16 @@ } }, "node_modules/@types/express": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", - "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", + "version": "5.0.6", "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^1" + "@types/serve-static": "^2" } }, "node_modules/@types/express-serve-static-core": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", - "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -888,26 +764,18 @@ }, "node_modules/@types/http-assert": { "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", - "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "license": "MIT" }, "node_modules/@types/keygrip": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", - "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", "license": "MIT" }, "node_modules/@types/koa": { "version": "2.15.0", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", - "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", "license": "MIT", "dependencies": { "@types/accepts": "*", @@ -922,98 +790,60 @@ }, "node_modules/@types/koa-compose": { "version": "3.2.9", - "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.9.tgz", - "integrity": "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA==", "license": "MIT", "dependencies": { "@types/koa": "*" } }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true, "license": "MIT" }, "node_modules/@types/multer": { "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", - "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/node": { - "version": "24.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", - "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "version": "25.6.0", "license": "MIT", - "peer": true, "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, "node_modules/@types/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "version": "2.2.0", "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "license": "MIT", - "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/steamid": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/steamid/-/steamid-2.0.4.tgz", - "integrity": "sha512-LC0oaiNq3gqMI18MV935CnlyHmDooKRszK56jHv0+b7EbJwf5k++YsD52zWeJUD8nuYqP9LpIgpXGiYBOxpcYQ==", "dev": true, "license": "MIT" }, "node_modules/@types/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", "dependencies": { @@ -1022,8 +852,6 @@ }, "node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -1035,8 +863,6 @@ }, "node_modules/acorn": { "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -1048,8 +874,6 @@ }, "node_modules/acorn-walk": { "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1061,8 +885,6 @@ }, "node_modules/ansi-regex": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -1074,8 +896,6 @@ }, "node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -1087,14 +907,10 @@ }, "node_modules/any-base": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -1107,28 +923,50 @@ }, "node_modules/arg": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true, "license": "MIT" }, "node_modules/array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, "node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "12.9.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", "engines": { @@ -1138,30 +976,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bindings": { + "version": "1.5.0", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.2", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1170,8 +1024,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -1181,10 +1033,30 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1192,8 +1064,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1205,8 +1075,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -1221,8 +1089,6 @@ }, "node_modules/chokidar": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -1244,10 +1110,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "1.1.4", + "license": "ISC" + }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -1261,8 +1129,6 @@ }, "node_modules/cliui/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -1271,8 +1137,6 @@ }, "node_modules/cliui/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -1287,15 +1151,11 @@ }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -1309,8 +1169,6 @@ }, "node_modules/cliui/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -1322,8 +1180,6 @@ }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1340,8 +1196,6 @@ }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1353,22 +1207,11 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/content-disposition": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -1379,8 +1222,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1388,8 +1229,6 @@ }, "node_modules/cookie": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1397,8 +1236,6 @@ }, "node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -1406,15 +1243,11 @@ }, "node_modules/create-require": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -1428,8 +1261,6 @@ }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1443,10 +1274,28 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1454,18 +1303,21 @@ }, "node_modules/destroy": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1474,8 +1326,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -1488,37 +1338,34 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/emoji-regex": { "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1526,8 +1373,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1535,8 +1380,6 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -1547,8 +1390,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -1557,32 +1398,34 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -1612,10 +1455,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -1627,8 +1472,6 @@ }, "node_modules/finalhandler": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -1644,8 +1487,6 @@ }, "node_modules/foreground-child": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { @@ -1661,8 +1502,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1670,17 +1509,17 @@ }, "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", "dev": true, "license": "MIT", "dependencies": { @@ -1709,8 +1548,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1718,8 +1555,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -1728,8 +1563,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -1752,8 +1585,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -1763,10 +1594,12 @@ "node": ">= 0.4" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "license": "MIT" + }, "node_modules/glob": { "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -1786,8 +1619,6 @@ }, "node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { @@ -1799,8 +1630,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1811,15 +1640,11 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/handlebars": { "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1840,8 +1665,6 @@ }, "node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", "engines": { @@ -1850,8 +1673,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1862,8 +1683,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -1874,8 +1693,6 @@ }, "node_modules/http-errors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "license": "MIT", "dependencies": { "depd": "2.0.0", @@ -1890,42 +1707,58 @@ }, "node_modules/http-errors/node_modules/statuses": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.2", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore-by-default": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true, "license": "ISC" }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", "license": "ISC" }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -1933,8 +1766,6 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { @@ -1946,8 +1777,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -1956,8 +1785,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { @@ -1966,8 +1793,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -1979,8 +1804,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -1989,14 +1812,10 @@ }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/is-what": { "version": "4.1.16", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", - "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", "dev": true, "license": "MIT", "engines": { @@ -2008,15 +1827,11 @@ }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2031,8 +1846,6 @@ }, "node_modules/jsonfile": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -2044,22 +1857,16 @@ }, "node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/make-error": { "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true, "license": "ISC" }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -2067,8 +1874,6 @@ }, "node_modules/media-typer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -2076,8 +1881,6 @@ }, "node_modules/merge-anything": { "version": "5.1.7", - "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", - "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2092,8 +1895,6 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -2104,8 +1905,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2113,8 +1912,6 @@ }, "node_modules/mime": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", "bin": { "mime": "cli.js" @@ -2125,8 +1922,6 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2134,8 +1929,6 @@ }, "node_modules/mime-types": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -2144,10 +1937,18 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -2162,9 +1963,6 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2172,24 +1970,26 @@ }, "node_modules/minipass": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", "license": "MIT" }, "node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2197,22 +1997,28 @@ }, "node_modules/neo-async": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, + "node_modules/node-abi": { + "version": "3.80.0", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "version": "3.1.14", "dev": true, "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", @@ -2231,34 +2037,41 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.5", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.2.5", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", "engines": { @@ -2267,8 +2080,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -2279,8 +2090,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -2291,8 +2100,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -2300,15 +2107,11 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -2316,8 +2119,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -2326,8 +2127,6 @@ }, "node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2343,8 +2142,6 @@ }, "node_modules/path-to-regexp": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", "funding": { "type": "opencollective", @@ -2353,8 +2150,6 @@ }, "node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { @@ -2364,10 +2159,32 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -2379,15 +2196,19 @@ }, "node_modules/pstree.remy": { "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true, "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.1", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -2401,48 +2222,69 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "version": "3.0.2", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, + "node_modules/rc": { + "version": "1.2.8", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "license": "MIT", "dependencies": { @@ -2454,14 +2296,10 @@ }, "node_modules/reflect-metadata": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -2470,8 +2308,6 @@ }, "node_modules/router": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -2486,8 +2322,6 @@ }, "node_modules/rxjs": { "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2496,8 +2330,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -2516,15 +2348,10 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/semver": { "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2535,8 +2362,6 @@ }, "node_modules/send": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "dependencies": { "debug": "^4.3.5", @@ -2557,8 +2382,6 @@ }, "node_modules/serve-static": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -2572,14 +2395,10 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -2591,8 +2410,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -2600,22 +2417,17 @@ } }, "node_modules/short-uuid": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-5.2.0.tgz", - "integrity": "sha512-296/Nzi4DmANh93iYBwT4NoYRJuHnKEzefrkSagQbTH/A6NTaB68hSPDjm5IlbI5dx9FXdmtqPcj6N5H+CPm6w==", + "version": "6.0.3", "license": "MIT", "dependencies": { - "any-base": "^1.1.0", - "uuid": "^9.0.1" + "any-base": "^1.1.0" }, "engines": { - "node": ">=14" + "node": ">=14.17.0" } }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2633,8 +2445,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2649,8 +2459,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -2667,8 +2475,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -2686,8 +2492,6 @@ }, "node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -2697,10 +2501,49 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, "license": "MIT", "dependencies": { @@ -2712,8 +2555,6 @@ }, "node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2722,8 +2563,6 @@ }, "node_modules/statuses": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -2731,17 +2570,20 @@ }, "node_modules/steamid": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/steamid/-/steamid-2.1.0.tgz", - "integrity": "sha512-ndt1cvuuSC+i8fcxVsmeyRlgGsR1QsoAuIXz+eabj8/Y4GIWE2+mgHA7Hys61JDHOxttfWtXHtN2m5TNYTlORg==", "license": "MIT", "engines": { "node": ">=12.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -2759,8 +2601,6 @@ "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -2774,8 +2614,6 @@ }, "node_modules/string-width-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -2784,15 +2622,11 @@ }, "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2804,8 +2638,6 @@ }, "node_modules/strip-ansi": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -2821,8 +2653,6 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2834,18 +2664,21 @@ }, "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", "dependencies": { @@ -2855,10 +2688,32 @@ "node": ">=4" } }, + "node_modules/tar-fs": { + "version": "2.1.4", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2870,8 +2725,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -2879,8 +2732,6 @@ }, "node_modules/touch": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, "license": "ISC", "bin": { @@ -2889,8 +2740,6 @@ }, "node_modules/ts-deepmerge": { "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz", - "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==", "dev": true, "license": "ISC", "engines": { @@ -2899,8 +2748,6 @@ }, "node_modules/ts-node": { "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2943,15 +2790,21 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD", "optional": true }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-is": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -2964,20 +2817,15 @@ }, "node_modules/typed-emitter": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", - "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", "license": "MIT", "optionalDependencies": { "rxjs": "*" } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.2", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2988,8 +2836,6 @@ }, "node_modules/uglify-js": { "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -3002,21 +2848,15 @@ }, "node_modules/undefsafe": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true, "license": "MIT" }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.19.2", "license": "MIT" }, "node_modules/universalify": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -3025,46 +2865,29 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true, "license": "MIT" }, "node_modules/validator": { "version": "13.15.20", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz", - "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -3072,8 +2895,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3081,8 +2902,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -3097,15 +2916,11 @@ }, "node_modules/wordwrap": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3123,8 +2938,6 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3141,8 +2954,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -3151,8 +2962,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -3167,15 +2976,11 @@ }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -3189,8 +2994,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3202,14 +3005,10 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.20.0", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -3229,8 +3028,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -3239,8 +3036,6 @@ }, "node_modules/yaml": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "bin": { @@ -3252,8 +3047,6 @@ }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -3271,8 +3064,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { @@ -3281,8 +3072,6 @@ }, "node_modules/yargs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -3291,15 +3080,11 @@ }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -3313,8 +3098,6 @@ }, "node_modules/yargs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3326,8 +3109,6 @@ }, "node_modules/yn": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, "license": "MIT", "engines": { diff --git a/backend/package.json b/backend/package.json index ee46da8..4a6b3cf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "tmt2-backend", - "version": "2.0.0", + "version": "3.0.0", "main": "dist/backend/src/index.js", "repository": "https://github.com/JensForstmann/tmt2", "author": "Jens Forstmann", @@ -12,21 +12,23 @@ }, "dependencies": { "@tsoa/runtime": "^6.6.0", - "express": "^5.1.0", - "short-uuid": "^5.2.0", + "better-sqlite3": "^12.9.0", + "express": "^5.2.1", + "short-uuid": "^6.0.3", "steamid": "^2.1.0", "typed-emitter": "^2.1.0", - "ws": "^8.18.3" + "ws": "^8.20.0" }, "devDependencies": { "@tsoa/cli": "^6.6.0", - "@types/debug": "^4.1.12", - "@types/express": "^5.0.5", - "@types/node": "^24.10.0", + "@types/better-sqlite3": "^7.6.13", + "@types/debug": "^4.1.13", + "@types/express": "^5.0.6", + "@types/node": "^25.6.0", "@types/steamid": "^2.0.4", "@types/ws": "^8.18.1", - "nodemon": "^3.1.10", + "nodemon": "^3.1.14", "ts-node": "^10.9.2", - "typescript": "^5.9.3" + "typescript": "^6.0.2" } } diff --git a/backend/src/auth.ts b/backend/src/auth.ts index 920ad9a..13dea4f 100644 --- a/backend/src/auth.ts +++ b/backend/src/auth.ts @@ -64,7 +64,7 @@ export const getGlobalToken = (token?: string) => { return tokens.get(token); }; -export const isValidMatchToken = async (token?: string, matchId?: string) => { +export const isValidMatchToken = (token?: string, matchId?: string) => { if (!token || !matchId) { return; } @@ -77,7 +77,7 @@ export const isValidMatchToken = async (token?: string, matchId?: string) => { return match.data.tmtSecret === token; } - const matchFromStorage = await MatchService.getFromStorage(matchId); + const matchFromStorage = MatchService.getMatchFromDatabase(matchId); if (matchFromStorage) { return matchFromStorage.tmtSecret === token; } @@ -92,7 +92,7 @@ export const expressAuthentication = async ( ): Promise => { if (securityName === 'bearer_token' || securityName === 'bearer_token_optional') { const bearerToken = req.get('Authorization'); - const result = await isAuthorized(bearerToken, req.params['id']); + const result = isAuthorized(bearerToken, req.params['id']); if (result) { return Promise.resolve(result); } @@ -107,10 +107,7 @@ export const expressAuthentication = async ( return Promise.reject({}); }; -export const isAuthorized = async ( - token?: string, - matchId?: string -): Promise => { +export const isAuthorized = (token?: string, matchId?: string): IAuthResponse | false => { const t = getGlobalToken(token); if (t) { return { @@ -119,7 +116,7 @@ export const isAuthorized = async ( }; } - if (await isValidMatchToken(token, matchId)) { + if (isValidMatchToken(token, matchId)) { return { type: 'MATCH', }; diff --git a/backend/src/commands.ts b/backend/src/commands.ts index 652a317..218ac7c 100644 --- a/backend/src/commands.ts +++ b/backend/src/commands.ts @@ -19,6 +19,7 @@ const Commands = [ 'TACTICAL', 'RESTART', 'VERSION', + 'ADMIN', '*', ] as const; export type TCommand = (typeof Commands)[number]; @@ -46,6 +47,7 @@ commandMapping.set('team', 'TEAM'); commandMapping.set('tac', 'TACTICAL'); commandMapping.set('restart', 'RESTART'); commandMapping.set('version', 'VERSION'); +commandMapping.set('admin', 'ADMIN'); export const getInternalCommandByUserCommand = (userCommand: string) => { return commandMapping.get(userCommand); diff --git a/backend/src/database.ts b/backend/src/database.ts new file mode 100644 index 0000000..91d4505 --- /dev/null +++ b/backend/src/database.ts @@ -0,0 +1,12 @@ +import path from 'node:path'; +import Database from 'better-sqlite3'; +import { STORAGE_FOLDER } from './storage'; +import { migration01 } from './migrations/01'; + +export const db = new Database(path.join(STORAGE_FOLDER, 'sqlite3.db')); +db.pragma('journal_mode = WAL'); +db.pragma('foreign_keys = ON'); + +export const runMigrations = () => { + migration01(); +}; diff --git a/backend/src/election.ts b/backend/src/election.ts index 2983695..b585ff2 100644 --- a/backend/src/election.ts +++ b/backend/src/election.ts @@ -29,10 +29,10 @@ export const create = ( state: 'NOT_STARTED', currentStep: 0, currentSubStep: 'MAP', - teamX: undefined, - teamY: undefined, + teamX: null, + teamY: null, remainingMaps: mapPool.map((map) => map.toLowerCase()), - currentStepMap: undefined, + currentStepMap: null, currentAgree: { teamA: null, teamB: null, @@ -306,7 +306,7 @@ const ensureTeamXY = (match: Match.Match, who: TWho, teamAB: TTeamAB) => { /** * returns undefined if both teams are valid */ -const getValidTeamAB = (match: Match.Match, who: TWho): TTeamAB | undefined => { +const getValidTeamAB = (match: Match.Match, who: TWho): TTeamAB | null => { switch (who) { case 'TEAM_A': return 'TEAM_A'; @@ -348,7 +348,7 @@ const onAgreeCommand: commands.CommandHandler = async (e) => { match.data.election.currentAgree.teamB !== null && match.data.election.currentAgree.teamA === match.data.election.currentAgree.teamB ) { - match.data.election.currentStepMap = match.data.election.remainingMaps[matchMap]; + match.data.election.currentStepMap = match.data.election.remainingMaps[matchMap]!; match.data.election.currentAgree.teamA = null; match.data.election.currentAgree.teamB = null; match.data.election.remainingMaps.splice(matchMap, 1); @@ -623,7 +623,7 @@ export const auto = async (match: Match.Match) => { const autoMap = async (match: Match.Match, currentElectionStep: IElectionStep) => { if (currentElectionStep.map.mode === 'FIXED') { match.data.election.currentStepMap = currentElectionStep.map.fixed; - Events.onElectionMapStep(match, 'FIXED', match.data.election.currentStepMap); + Events.onElectionMapStep(match, 'FIXED', match.data.election.currentStepMap, null); await Match.say( match, `${match.data.matchMaps.length + 1}. MAP: ${formatMapName( @@ -643,11 +643,12 @@ const autoMap = async (match: Match.Match, currentElectionStep: IElectionStep) = match.data.election.remainingMaps.length ); if (currentElectionStep.map.mode === 'RANDOM_PICK') { - match.data.election.currentStepMap = match.data.election.remainingMaps[matchMap]; + match.data.election.currentStepMap = match.data.election.remainingMaps[matchMap]!; Events.onElectionMapStep( match, 'RANDOM_PICK', - match.data.election.remainingMaps[matchMap]! + match.data.election.remainingMaps[matchMap]!, + null ); await Match.say( match, @@ -659,7 +660,8 @@ const autoMap = async (match: Match.Match, currentElectionStep: IElectionStep) = Events.onElectionMapStep( match, 'RANDOM_BAN', - match.data.election.remainingMaps[matchMap]! + match.data.election.remainingMaps[matchMap]!, + null ); await Match.say( match, @@ -685,6 +687,8 @@ const autoSide = async (match: Match.Match, currentElectionStep: IElectionStep) Events.onElectionSideStep(match, 'FIXED', { ctTeam: Match.getTeamByAB(match, 'TEAM_A'), tTeam: Match.getTeamByAB(match, 'TEAM_B'), + pickerTeam: null, + pickerSide: null, }); match.data.matchMaps.push(MatchMap.create(currentStepMap, false, 'TEAM_A')); await Match.say( @@ -701,6 +705,8 @@ const autoSide = async (match: Match.Match, currentElectionStep: IElectionStep) Events.onElectionSideStep(match, 'FIXED', { ctTeam: Match.getTeamByAB(match, 'TEAM_B'), tTeam: Match.getTeamByAB(match, 'TEAM_A'), + pickerTeam: null, + pickerSide: null, }); match.data.matchMaps.push(MatchMap.create(currentStepMap, false, 'TEAM_B')); await Match.say( @@ -718,6 +724,8 @@ const autoSide = async (match: Match.Match, currentElectionStep: IElectionStep) Events.onElectionSideStep(match, 'FIXED', { ctTeam: Match.getTeamByAB(match, match.data.election.teamX), tTeam: Match.getTeamByAB(match, match.data.election.teamY), + pickerTeam: null, + pickerSide: null, }); match.data.matchMaps.push( MatchMap.create(currentStepMap, false, match.data.election.teamX) @@ -738,6 +746,8 @@ const autoSide = async (match: Match.Match, currentElectionStep: IElectionStep) Events.onElectionSideStep(match, 'FIXED', { ctTeam: Match.getTeamByAB(match, match.data.election.teamY), tTeam: Match.getTeamByAB(match, match.data.election.teamX), + pickerTeam: null, + pickerSide: null, }); match.data.matchMaps.push( MatchMap.create(currentStepMap, false, match.data.election.teamY) @@ -757,7 +767,7 @@ const autoSide = async (match: Match.Match, currentElectionStep: IElectionStep) } if (currentElectionStep.side.mode === 'KNIFE') { - Events.onElectionSideStep(match, 'KNIFE'); + Events.onElectionSideStep(match, 'KNIFE', null); match.data.matchMaps.push(MatchMap.create(currentStepMap, true)); await Match.say( match, @@ -774,6 +784,8 @@ const autoSide = async (match: Match.Match, currentElectionStep: IElectionStep) Events.onElectionSideStep(match, 'RANDOM', { ctTeam: Match.getTeamByAB(match, startAsCtTeam), tTeam: Match.getTeamByAB(match, getOtherTeamAB(startAsCtTeam)), + pickerTeam: null, + pickerSide: null, }); match.data.matchMaps.push(MatchMap.create(currentStepMap, false, startAsCtTeam)); await Match.say( diff --git a/backend/src/events.ts b/backend/src/events.ts index 61a75b5..80c54b3 100644 --- a/backend/src/events.ts +++ b/backend/src/events.ts @@ -6,6 +6,7 @@ import { ElectionSideStep, Event, EventType, + IGameServer, IMatchMap, IPlayer, ITeam, @@ -22,23 +23,17 @@ import { TSideMode, TTeamString, } from '../../common'; +import { db } from './database'; import * as Match from './match'; import * as MatchService from './matchService'; import { Settings } from './settings'; -import * as Storage from './storage'; import * as WebSocket from './webSocket'; -const STORAGE_EVENTS_PREFIX = 'events_'; -const STORAGE_EVENTS_SUFFIX = '.jsonl'; +const send = (match: Match.Match, data: Event) => { + saveEventToDb(data); -const send = (match: Match.Match, data: Event, isSystemEvent?: boolean) => { - // Storage - Storage.appendLine(STORAGE_EVENTS_PREFIX + match.data.id + STORAGE_EVENTS_SUFFIX, data); + WebSocket.publish(data); - // WebSocket - WebSocket.publish(data, isSystemEvent); - - // WebHook const url = match.data.webhookUrl; if (url?.startsWith('http') && Settings.WEBHOOK_EVENTS.includes(data.type)) { fetch(url, { @@ -56,14 +51,6 @@ const send = (match: Match.Match, data: Event, isSystemEvent?: boolean) => { } }; -export const getEventsTail = async (matchId: string, numberOfLines = 1000): Promise => { - return await Storage.readLines( - STORAGE_EVENTS_PREFIX + matchId + STORAGE_EVENTS_SUFFIX, - [], - numberOfLines - ); -}; - const getBaseEvent = ( match: Match.Match, type: T @@ -88,7 +75,7 @@ export const onElectionMapStep = ( match: Match.Match, mode: TMapMode, mapName: string, - pickerTeam?: ITeam + pickerTeam: ITeam | null ) => { const data: ElectionMapStep = { ...getBaseEvent(match, 'ELECTION_MAP_STEP'), @@ -102,14 +89,15 @@ export const onElectionMapStep = ( export const onElectionSideStep = ( match: Match.Match, mode: TSideMode, - options?: Omit + options: Omit | null ) => { const data: ElectionSideStep = { ...getBaseEvent(match, 'ELECTION_SIDE_STEP'), mode: mode, - pickerTeam: options?.pickerTeam, - ctTeam: options?.ctTeam, - tTeam: options?.tTeam, + pickerTeam: options?.pickerTeam ?? null, + pickerSide: options?.pickerSide ?? null, + ctTeam: options?.ctTeam ?? null, + tTeam: options?.tTeam ?? null, }; send(match, data); }; @@ -163,6 +151,7 @@ export const onConsoleSay = (match: Match.Match, message: string) => { playerTeam: null, message: message, isTeamChat: false, + teamString: null, }; send(match, data); }; @@ -237,24 +226,96 @@ export const onMatchCreate = (match: Match.Match) => { ...getBaseEvent(match, 'MATCH_CREATE'), match: { ...MatchService.hideRconPassword(match.data, false), - isLive: true, }, }; - send(match, data, true); + send(match, data); }; export const onMatchUpdate = (match: Match.Match, path: Array, value: any) => { + // change value if it's a rconPassword and the hideRconPassword flag is set + const firstPath = path[0] as keyof Match.Match['data'] | undefined; + const secondPath = path[1] as keyof Match.Match['data']['gameServer'] | undefined; + if (match.data.gameServer.hideRconPassword) { + if (firstPath === 'gameServer' && secondPath === 'rconPassword') { + // only exactly the rconPassword is updated + (value as IGameServer['rconPassword']) = ''; + } else if (firstPath === 'gameServer' && secondPath === undefined) { + // complete gameServer object is updated + (value as IGameServer).rconPassword = ''; + } + } + const data: MatchUpdateEvent = { ...getBaseEvent(match, 'MATCH_UPDATE'), path: path, value: value, }; - // send as a system event if the match was created less than 10 seconds ago - const sendAsSysEvent = match.data.createdAt + 10000 > Date.now(); - send(match, data, sendAsSysEvent); + send(match, data); }; export const onMatchStop = (match: Match.Match) => { const data: MatchStopEvent = getBaseEvent(match, 'MATCH_STOP'); send(match, data); }; + +const eventToDb = (event: Event): TDbEvent => { + const copy: any = { ...event }; + delete copy.timestamp; + delete copy.matchId; + delete copy.matchPassthrough; + delete copy.type; + const payload = JSON.stringify(copy); + return { + timestamp: event.timestamp, + matchId: event.matchId, + matchPassthrough: event.matchPassthrough, + type: event.type, + payload: payload, + }; +}; + +const eventFromDb = (dbEvent: TDbEvent): Event => { + return { + ...JSON.parse((dbEvent as any).payload), + timestamp: dbEvent.timestamp, + matchId: dbEvent.matchId, + matchPassthrough: dbEvent.matchPassthrough, + type: dbEvent.type, + }; +}; + +type TDbEvent = { + timestamp: string; + matchId: string; + matchPassthrough: string | null; + type: string; + payload: string; +}; + +export const saveEventToDb = (event: Event) => { + db.prepare( + `INSERT INTO event ( + timestamp, + matchId, + matchPassthrough, + type, + payload + ) VALUEs ( + :timestamp, + :matchId, + :matchPassthrough, + :type, + :payload + )` + ).run(eventToDb(event)); +}; + +export const getLatestEventsFromDatabase = (matchId: string, numberOfEvents = 1000): Event[] => { + const rows = db + .prepare< + { matchId: string; notType: EventType; numberOfEvents: number }, + TDbEvent + >(`SELECT * FROM event WHERE matchId = :matchId AND type != :notType ORDER BY id DESC LIMIT :numberOfEvents`) + .all({ matchId: matchId, notType: 'MATCH_UPDATE', numberOfEvents: numberOfEvents }); + return rows.map(eventFromDb).reverse(); +}; diff --git a/backend/src/gameServer.ts b/backend/src/gameServer.ts index 7f77b2d..bee7770 100644 --- a/backend/src/gameServer.ts +++ b/backend/src/gameServer.ts @@ -21,7 +21,10 @@ export const removeColors = (string: string) => { return string; }; -export const create = async (dto: IGameServer, log: (msg: string) => void): Promise => { +export const create = async ( + dto: Pick, + log: (msg: string) => void +): Promise => { const rcon = new Rcon({ host: dto.ip, port: dto.port, @@ -120,6 +123,6 @@ export const disconnect = async (match: Match.Match) => { } }; -export const formatMapName = (mapName: string | undefined) => { +export const formatMapName = (mapName: string | null | undefined) => { return colors.grey + parseMapParts(mapName ?? '').external + colors.white; }; diff --git a/backend/src/gameServersController.ts b/backend/src/gameServersController.ts index 1d33979..fe5b4d4 100644 --- a/backend/src/gameServersController.ts +++ b/backend/src/gameServersController.ts @@ -35,14 +35,14 @@ export class GameServersController extends Controller { @Body() requestBody: IManagedGameServerCreateDto ): Promise { const managedGameServer: IManagedGameServer = { - canBeUsed: true, ...requestBody, + canBeUsed: requestBody.canBeUsed ?? true, usedBy: null, }; if (!requestBody.ip) { throw 'invalid ip'; } - await ManagedGameServers.add(managedGameServer); + ManagedGameServers.add(managedGameServer); return managedGameServer; } @@ -55,7 +55,7 @@ export class GameServersController extends Controller { ip: string, port: number ): Promise { - return await ManagedGameServers.update(requestBody); + return ManagedGameServers.update(requestBody); } /** @@ -63,7 +63,7 @@ export class GameServersController extends Controller { */ @Delete('{ip}/{port}') async deleteGameServer(ip: string, port: number): Promise { - await ManagedGameServers.remove({ + ManagedGameServers.remove({ ip: ip, port: port, }); diff --git a/backend/src/index.ts b/backend/src/index.ts index 03b7bee..da60ee8 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -14,6 +14,8 @@ import * as Presets from './presets'; import { RegisterRoutes } from './routes'; import * as Storage from './storage'; import * as WebSocket from './webSocket'; +import * as Database from './database'; +Database.runMigrations(); export const TMT_LOG_ADDRESS: string | null = (() => { if (!process.env['TMT_LOG_ADDRESS']) { @@ -127,7 +129,7 @@ const main = async () => { await Storage.setup(); await Auth.setup(); await WebSocket.setup(httpServer); - await ManagedGameServers.setup(); + ManagedGameServers.setup(); await Presets.setup(); Match.registerCommandHandlers(); MatchMap.registerCommandHandlers(); diff --git a/backend/src/managedGameServers.ts b/backend/src/managedGameServers.ts index 198018b..ee4064f 100644 --- a/backend/src/managedGameServers.ts +++ b/backend/src/managedGameServers.ts @@ -1,21 +1,18 @@ import { IGameServer, IManagedGameServer, IManagedGameServerUpdateDto } from '../../common'; +import { db } from './database'; import * as GameServer from './gameServer'; -import * as Storage from './storage'; -const FILE_NAME = 'managed_game_servers.json'; const managedGameServers = new Map(); -const write = async () => { - await Storage.write(FILE_NAME, Array.from(managedGameServers.values())); -}; - const key = (gameServer: IManagedGameServerUpdateDto) => { return gameServer.ip + ':' + gameServer.port; }; -export const setup = async () => { - const data = await Storage.read(FILE_NAME, [] as IManagedGameServer[]); - data.forEach((managedGameServer) => add(managedGameServer, false)); +export const setup = () => { + const rows = db.prepare<[], TDbManagedGameServer>('SELECT * FROM managedGameServer').all(); + rows.map((row) => managedGameServerFromDb(row)).forEach((managedGameServer) => + add(managedGameServer, false) + ); }; export const get = (ip: string, port: number) => { @@ -26,41 +23,43 @@ export const getAll = () => { return Array.from(managedGameServers.values()); }; -export const add = async (managedGameServer: IManagedGameServer, writeToDisk = true) => { +export const add = (managedGameServer: IManagedGameServer, writeToDisk = true) => { if (managedGameServers.has(key(managedGameServer))) { throw 'this is already a managed game server'; } managedGameServers.set(key(managedGameServer), managedGameServer); if (writeToDisk) { - await write(); + saveManagedGameServerToDb(managedGameServer); } }; -export const update = async (dto: IManagedGameServerUpdateDto) => { +export const update = (dto: IManagedGameServerUpdateDto) => { const managedGameServer = managedGameServers.get(key(dto)); if (!managedGameServer) { throw 'this is not a managed game server'; } const updated = { ...managedGameServer, ...dto }; managedGameServers.set(key(dto), updated); - await write(); + saveManagedGameServerToDb(updated); return updated; }; -export const remove = async (gameServer: IManagedGameServerUpdateDto) => { +export const remove = (gameServer: IManagedGameServerUpdateDto) => { const removed = managedGameServers.delete(key(gameServer)); if (removed) { - await write(); + db.prepare<{ ip: string; port: number }>( + 'DELETE FROM managedGameServer WHERE ip = :ip AND port = :port' + ).run({ ip: gameServer.ip, port: gameServer.port }); } }; -export const getFree = async (matchId: string): Promise => { +export const getFree = (matchId: string): IGameServer | undefined => { const free = getAll().find( (managedGameServer) => managedGameServer.usedBy === null && managedGameServer.canBeUsed ); if (free) { free.usedBy = matchId; - await write(); + saveManagedGameServerToDb(free); return { ip: free.ip, port: free.port, @@ -68,14 +67,14 @@ export const getFree = async (matchId: string): Promise hideRconPassword: true, }; } - return; + return undefined; }; -export const free = async (gameServer: IManagedGameServerUpdateDto, matchId: string) => { +export const free = (gameServer: IManagedGameServerUpdateDto, matchId: string) => { const managedGameServer = managedGameServers.get(key(gameServer)); if (managedGameServer?.usedBy === matchId) { managedGameServer.usedBy = null; - await write(); + saveManagedGameServerToDb(managedGameServer); } }; @@ -103,3 +102,57 @@ export const execManyRcon = async (managedGameServer: IManagedGameServer, comman return responses; }; + +export type TDbManagedGameServer = { + ip: string; + port: number; + rconPassword: string; + canBeUsed: number; + usedBy: string | null; +}; + +const managedGameServerToDb = (managedGameServer: IManagedGameServer): TDbManagedGameServer => { + return { + ip: managedGameServer.ip, + port: managedGameServer.port, + rconPassword: managedGameServer.rconPassword, + canBeUsed: managedGameServer.canBeUsed ? 1 : 0, + usedBy: managedGameServer.usedBy, + }; +}; + +const managedGameServerFromDb = (dbManagedGameServer: TDbManagedGameServer): IManagedGameServer => { + return { + ip: dbManagedGameServer.ip, + port: dbManagedGameServer.port, + rconPassword: dbManagedGameServer.rconPassword, + canBeUsed: !!dbManagedGameServer.canBeUsed, + usedBy: dbManagedGameServer.usedBy, + }; +}; + +const saveManagedGameServerToDb = (managedGameServer: IManagedGameServer) => { + db.prepare( + ` + INSERT INTO managedGameServer ( + ip, + port, + rconPassword, + canBeUsed, + usedBy + ) VALUES ( + :ip, + :port, + :rconPassword, + :canBeUsed, + :usedBy + ) ON CONFLICT (ip, port) DO UPDATE SET + ip = :ip, + port = :port, + rconPassword = :rconPassword, + canBeUsed = :canBeUsed, + usedBy = :usedBy + WHERE ip = :ip AND port = :port + ` + ).run(managedGameServerToDb(managedGameServer)); +}; diff --git a/backend/src/match.ts b/backend/src/match.ts index 219ea3b..a926876 100644 --- a/backend/src/match.ts +++ b/backend/src/match.ts @@ -2,11 +2,16 @@ import { ValidateError } from '@tsoa/runtime'; import { generate as shortUuid } from 'short-uuid'; import { COMMIT_SHA, IMAGE_BUILD_TIMESTAMP, TMT_LOG_ADDRESS, VERSION } from '.'; import { + IElectionStep, + IGameServer, IMatch, IMatchCreateDto, IMatchUpdateDto, IPlayer, ITeam, + TMatchEndAction, + TMatchMode, + TMatchState, TTeamAB, TTeamString, escapeRconSayString, @@ -26,12 +31,10 @@ import * as MatchService from './matchService'; import * as Player from './player'; import { Rcon } from './rcon-client'; import { Settings } from './settings'; -import * as Storage from './storage'; import * as Team from './team'; +import { db } from './database'; -const STORAGE_LOGS_PREFIX = 'logs_'; -const STORAGE_LOGS_SUFFIX = '.jsonl'; -const SAY_PREFIX = GameServer.colors.green + Settings.SAY_PREFIX + GameServer.colors.white; +const SAY_PREFIX = () => GameServer.colors.green + Settings.SAY_PREFIX + GameServer.colors.white; export interface Match { data: IMatch; @@ -54,6 +57,7 @@ export const createFromData = async (data: IMatch, logMessage?: string) => { warnAboutWrongTeam: true, }; match.data = addChangeListener(data, createOnDataChangeHandler(match)); + await MatchService.save(data); match.log = createLogger(match); if (logMessage) { match.log(logMessage); @@ -81,8 +85,13 @@ export const createFromData = async (data: IMatch, logMessage?: string) => { return match; }; -export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logSecret: string) => { - const gameServer = dto.gameServer ?? (await ManagedGameServers.getFree(id)); +export const createNew = async (dto: IMatchCreateDto, id: string, logSecret: string) => { + const gameServer = dto.gameServer + ? { + ...dto.gameServer, + hideRconPassword: false, + } + : ManagedGameServers.getFree(id); if (!gameServer) { throw 'no free game server available'; } @@ -90,6 +99,7 @@ export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logS ...dto, gameServer: gameServer, id: id, + passthrough: dto.passthrough ?? null, teamA: Team.createFromCreateDto(dto.teamA), teamB: Team.createFromCreateDto(dto.teamB), state: 'ELECTION', @@ -97,7 +107,6 @@ export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logS parseIncomingLogs: false, matchMaps: [], currentMap: 0, - logs: [], players: [], rconCommands: { init: dto.rconCommands?.init ?? [], @@ -111,21 +120,25 @@ export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logS isStopped: false, tmtSecret: shortUuid(), serverPassword: '', - tmtLogAddress: dto.tmtLogAddress, + tmtLogAddress: dto.tmtLogAddress ?? null, createdAt: Date.now(), lastSavedAt: 0, webhookUrl: dto.webhookUrl ?? null, - webhookHeaders: dto.webhookHeaders ?? null, + webhookHeaders: dto.webhookHeaders ?? {}, mode: dto.mode ?? 'SINGLE', + needsAttentionSince: null, + isLive: false, }; try { const match = await createFromData(data, 'Create new match'); return match; } catch (err) { if (!dto.gameServer) { - await ManagedGameServers.free(gameServer, id); - await ManagedGameServers.update({ ...gameServer, canBeUsed: false }); + ManagedGameServers.free(gameServer, id); + ManagedGameServers.update({ ...gameServer, canBeUsed: false }); } + data.isStopped = true; + saveMatchToDb(data); throw err; } }; @@ -133,7 +146,6 @@ export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logS const createLogger = (match: Match) => (msg: string) => { const ds = new Date().toISOString(); msg = GameServer.removeColors(msg); - Storage.appendLine(STORAGE_LOGS_PREFIX + match.data.id + STORAGE_LOGS_SUFFIX, `${ds} | ${msg}`); console.info(`${ds} [${match.data.id}] ${msg}`); Events.onLog(match, msg); }; @@ -142,14 +154,6 @@ const createOnDataChangeHandler = (match: Match) => (path: Array => { - return await Storage.readLines( - STORAGE_LOGS_PREFIX + matchId + STORAGE_LOGS_SUFFIX, - [], - numberOfLines - ); -}; - const connectToGameServer = async (match: Match): Promise => { const addr = `${match.data.gameServer.ip}:${match.data.gameServer.port}`; match.log(`Connect rcon ${addr}`); @@ -325,7 +329,7 @@ export const execRconCommands = async (match: Match, key: keyof IMatch['rconComm }; export const say = async (match: Match, message: string) => { - message = escapeRconSayString(SAY_PREFIX + message); + message = escapeRconSayString(SAY_PREFIX() + message); await execRcon(match, `say ${message}`); }; @@ -551,6 +555,24 @@ const onLogLine = async (match: Match, line: string) => { match.warnAboutWrongTeam = true; return; } + + // Match pause is disabled - TimeOutCTs + // Match pause is disabled - TimeOutTs + const matchPauseDisabledPattern = /Match pause is disabled.*/; + const matchPauseDisabledMatch = line.match( + new RegExp(dateTimePattern.source + matchPauseDisabledPattern.source) + ); + if (matchPauseDisabledMatch) { + // Workaround: Match map being in wrong (paused) state when ingame vote for tactical timeout is used together with TMT's `.pause` command. + const currentMatchMap = getCurrentMatchMap(match); + if (currentMatchMap && currentMatchMap.state === 'PAUSED') { + currentMatchMap.readyTeams.teamA = false; + currentMatchMap.readyTeams.teamB = false; + currentMatchMap.state = 'IN_PROGRESS'; + MatchService.scheduleSave(match); + } + return; + } } catch (err) { match.log('Error in onLogLine' + err); } @@ -719,18 +741,34 @@ const onPlayerSay = async ( const onConsoleSay = async (match: Match, message: string) => { message = message.trim(); - if (!message.startsWith(SAY_PREFIX)) { + if (!message.startsWith(SAY_PREFIX())) { message = GameServer.removeColors(message); Events.onConsoleSay(match, message); } }; export const registerCommandHandlers = () => { + commands.registerHandler('ADMIN', onAdminCommand); commands.registerHandler('TEAM', onTeamCommand); commands.registerHandler('VERSION', onVersionCommand); commands.registerHandler('*', onEveryCommand); }; +const onAdminCommand: commands.CommandHandler = async (e) => { + if (e.match.data.needsAttentionSince !== null) { + await say(e.match, 'ADMIN WAS ALREADY NOTIFIED'); + } else { + e.match.data.needsAttentionSince = Date.now(); + MatchService.scheduleSave(e.match); + await say(e.match, `${GameServer.colors.red}ADMIN NOTIFIED`); + } + + const currentMatchMap = getCurrentMatchMap(e.match); + if (currentMatchMap && currentMatchMap.state !== 'PAUSED') { + await say(e.match, `YOU CAN ${commands.formatFirstIngameCommand('PAUSE')} IF NEEDED `); + } +}; + const onVersionCommand: commands.CommandHandler = async (e) => { await say( e.match, @@ -986,7 +1024,7 @@ export const stop = async (match: Match) => { }); await say(match, `TMT IS OFFLINE`).catch(() => {}); await GameServer.disconnect(match); - await ManagedGameServers.free(match.data.gameServer, match.data.id); + ManagedGameServers.free(match.data.gameServer, match.data.id); Events.onMatchStop(match); }; @@ -1023,15 +1061,11 @@ const restartElection = async (match: Match) => { const loopMatch = async (match: Match) => { match.log('Loop mode is set, restart match'); await restartElection(match); - sleep(1000) - .then(() => { - // delay, because there might still be events left - match.log('Clear player list'); - match.data.players = []; - }) - .catch(() => { - // shouldn't throw - }); + sleep(1000).then(() => { + // delay, because there might still be events left + match.log('Clear player list'); + match.data.players = []; + }); }; /** @@ -1081,8 +1115,12 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { } if (dto.gameServer) { - await ManagedGameServers.free(match.data.gameServer, match.data.id); - match.data.gameServer = dto.gameServer; + ManagedGameServers.free(match.data.gameServer, match.data.id); + const gameServer: IGameServer = { + ...dto.gameServer, + hideRconPassword: match.data.gameServer.hideRconPassword, + }; + match.data.gameServer = gameServer; match.rconConnection?.end().catch((err) => { match.log(`Error end rcon connection ${err}`); }); @@ -1097,7 +1135,7 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { match.data.webhookUrl = dto.webhookUrl; } - if (dto.webhookHeaders !== undefined) { + if (dto.webhookHeaders) { match.data.webhookHeaders = dto.webhookHeaders; } @@ -1128,7 +1166,7 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { await ensureLogAddressIsRegistered(match); } - if (dto.currentMap !== undefined && dto.currentMap !== match.data.currentMap) { + if (typeof dto.currentMap === 'number' && dto.currentMap !== match.data.currentMap) { const nextMap = match.data.matchMaps[dto.currentMap]; if (nextMap) { match.data.currentMap = dto.currentMap; @@ -1161,7 +1199,7 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { match.data.rconCommands.end = dto.rconCommands.end; } - if (dto.canClinch !== undefined) { + if (typeof dto.canClinch === 'boolean') { match.data.canClinch = dto.canClinch; if (isMatchEnd(match)) { await onMatchEnd(match); @@ -1172,6 +1210,10 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { match.data.matchEndAction = dto.matchEndAction; } + if (dto.needsAttentionSince !== undefined) { + match.data.needsAttentionSince = dto.needsAttentionSince; + } + if (dto._restartElection) { await restartElection(match); } @@ -1191,6 +1233,250 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { if (dto._execRconCommandsEnd) { await execRconCommands(match, 'end'); } +}; - MatchService.scheduleSave(match); +export type TDbMatch = { + id: string; + state: string; + passthrough: string | null; + mapPool: string; + teamAPassthrough: string | null; + teamAName: string; + teamAAdvantage: number; + teamAPlayerSteamIds64: string; + teamBPassthrough: string | null; + teamBName: string; + teamBAdvantage: number; + teamBPlayerSteamIds64: string; + electionSteps: string; + gameServerIp: string; + gameServerPort: number; + gameServerRconPassword: string; + gameServerHideRconPassword: number; + logSecret: string; + currentMap: number; + webhookUrl: string | null; + webhookHeaders: string; + rconCommandsInit: string; + rconCommandsKnife: string; + rconCommandsMatch: string; + rconCommandsEnd: string; + canClinch: number; + matchEndAction: string; + tmtSecret: string; + isStopped: number; + tmtLogAddress: string | null; + createdAt: number; + lastSavedAt: number; + mode: string; + needsAttentionSince: number | null; +}; + +const matchToDb = (match: IMatch): TDbMatch => { + return { + id: match.id, + state: match.state, + passthrough: match.passthrough, + mapPool: JSON.stringify(match.mapPool), + teamAPassthrough: match.teamA.passthrough, + teamAName: match.teamA.name, + teamAAdvantage: match.teamA.advantage, + teamAPlayerSteamIds64: JSON.stringify(match.teamA.playerSteamIds64), + teamBPassthrough: match.teamB.passthrough, + teamBName: match.teamB.name, + teamBAdvantage: match.teamB.advantage, + teamBPlayerSteamIds64: JSON.stringify(match.teamB.playerSteamIds64), + electionSteps: JSON.stringify(match.electionSteps), + gameServerIp: match.gameServer.ip, + gameServerPort: match.gameServer.port, + gameServerRconPassword: match.gameServer.rconPassword, + gameServerHideRconPassword: match.gameServer.hideRconPassword ? 1 : 0, + logSecret: match.logSecret, + currentMap: match.currentMap, + webhookUrl: match.webhookUrl, + webhookHeaders: JSON.stringify(match.webhookHeaders), + rconCommandsInit: JSON.stringify(match.rconCommands.init), + rconCommandsKnife: JSON.stringify(match.rconCommands.knife), + rconCommandsMatch: JSON.stringify(match.rconCommands.match), + rconCommandsEnd: JSON.stringify(match.rconCommands.end), + canClinch: match.canClinch ? 1 : 0, + matchEndAction: match.matchEndAction, + tmtSecret: match.tmtSecret, + isStopped: match.isStopped ? 1 : 0, + tmtLogAddress: match.tmtLogAddress, + createdAt: match.createdAt, + lastSavedAt: match.lastSavedAt ?? Date.now(), + mode: match.mode, + needsAttentionSince: match.needsAttentionSince, + }; +}; + +export const matchFromDb = ( + dbMatch: TDbMatch, + dbMatchMaps: MatchMap.TDbMatchMap[], + dbMatchPlayers: Player.TDbMatchPlayer[] +): IMatch => { + const mapPool = JSON.parse(dbMatch.mapPool) as string[]; + const electionSteps = JSON.parse(dbMatch.electionSteps) as IElectionStep[]; + return { + id: dbMatch.id, + state: dbMatch.state as TMatchState, + passthrough: dbMatch.passthrough, + mapPool: mapPool, + teamA: { + passthrough: dbMatch.teamAPassthrough, + name: dbMatch.teamAName, + advantage: dbMatch.teamAAdvantage, + playerSteamIds64: JSON.parse(dbMatch.teamAPlayerSteamIds64) as string[], + }, + teamB: { + passthrough: dbMatch.teamBPassthrough, + name: dbMatch.teamBName, + advantage: dbMatch.teamBAdvantage, + playerSteamIds64: JSON.parse(dbMatch.teamBPlayerSteamIds64) as string[], + }, + parseIncomingLogs: false, + matchMaps: dbMatchMaps.map(MatchMap.matchMapFromDb), + players: dbMatchPlayers.map(Player.matchPlayerFromDb), + electionSteps: electionSteps, + election: Election.create(mapPool, electionSteps), + gameServer: { + ip: dbMatch.gameServerIp, + port: dbMatch.gameServerPort, + rconPassword: dbMatch.gameServerRconPassword, + hideRconPassword: !!dbMatch.gameServerHideRconPassword, + }, + logSecret: dbMatch.logSecret, + currentMap: dbMatch.currentMap, + webhookUrl: dbMatch.webhookUrl, + webhookHeaders: JSON.parse(dbMatch.webhookHeaders) as { [key: string]: string }, + rconCommands: { + init: JSON.parse(dbMatch.rconCommandsInit) as string[], + knife: JSON.parse(dbMatch.rconCommandsKnife) as string[], + match: JSON.parse(dbMatch.rconCommandsMatch) as string[], + end: JSON.parse(dbMatch.rconCommandsEnd) as string[], + }, + canClinch: !!dbMatch.canClinch, + matchEndAction: dbMatch.matchEndAction as TMatchEndAction, + tmtSecret: dbMatch.tmtSecret, + isStopped: !!dbMatch.isStopped, + tmtLogAddress: dbMatch.tmtLogAddress, + createdAt: dbMatch.createdAt, + mode: dbMatch.mode as TMatchMode, + serverPassword: '', + lastSavedAt: 0, + needsAttentionSince: dbMatch.needsAttentionSince, + isLive: false, + }; +}; + +export const saveMatchToDb = (match: IMatch) => { + db.prepare( + `INSERT INTO match ( + id, + state, + passthrough, + mapPool, + teamAPassthrough, + teamAName, + teamAAdvantage, + teamAPlayerSteamIds64, + teamBPassthrough, + teamBName, + teamBAdvantage, + teamBPlayerSteamIds64, + electionSteps, + gameServerIp, + gameServerPort, + gameServerRconPassword, + gameServerHideRconPassword, + logSecret, + currentMap, + webhookUrl, + webhookHeaders, + rconCommandsInit, + rconCommandsKnife, + rconCommandsMatch, + rconCommandsEnd, + canClinch, + matchEndAction, + tmtSecret, + isStopped, + tmtLogAddress, + createdAt, + lastSavedAt, + mode, + needsAttentionSince + ) VALUES ( + :id, + :state, + :passthrough, + :mapPool, + :teamAPassthrough, + :teamAName, + :teamAAdvantage, + :teamAPlayerSteamIds64, + :teamBPassthrough, + :teamBName, + :teamBAdvantage, + :teamBPlayerSteamIds64, + :electionSteps, + :gameServerIp, + :gameServerPort, + :gameServerRconPassword, + :gameServerHideRconPassword, + :logSecret, + :currentMap, + :webhookUrl, + :webhookHeaders, + :rconCommandsInit, + :rconCommandsKnife, + :rconCommandsMatch, + :rconCommandsEnd, + :canClinch, + :matchEndAction, + :tmtSecret, + :isStopped, + :tmtLogAddress, + :createdAt, + :lastSavedAt, + :mode, + :needsAttentionSince + ) ON CONFLICT (id) DO UPDATE SET + state = :state, + passthrough = :passthrough, + mapPool = :mapPool, + teamAPassthrough = :teamAPassthrough, + teamAName = :teamAName, + teamAAdvantage = :teamAAdvantage, + teamAPlayerSteamIds64 = :teamAPlayerSteamIds64, + teamBPassthrough = :teamBPassthrough, + teamBName = :teamBName, + teamBAdvantage = :teamBAdvantage, + teamBPlayerSteamIds64 = :teamBPlayerSteamIds64, + electionSteps = :electionSteps, + gameServerIp = :gameServerIp, + gameServerPort = :gameServerPort, + gameServerRconPassword = :gameServerRconPassword, + gameServerHideRconPassword = :gameServerHideRconPassword, + logSecret = :logSecret, + currentMap = :currentMap, + webhookUrl = :webhookUrl, + webhookHeaders = :webhookHeaders, + rconCommandsInit = :rconCommandsInit, + rconCommandsKnife = :rconCommandsKnife, + rconCommandsMatch = :rconCommandsMatch, + rconCommandsEnd = :rconCommandsEnd, + canClinch = :canClinch, + matchEndAction = :matchEndAction, + tmtSecret = :tmtSecret, + isStopped = :isStopped, + tmtLogAddress = :tmtLogAddress, + createdAt = :createdAt, + lastSavedAt = :lastSavedAt, + mode = :mode, + needsAttentionSince = :needsAttentionSince + WHERE id = :id + ` + ).run(matchToDb(match)); }; diff --git a/backend/src/matchMap.ts b/backend/src/matchMap.ts index ea37df0..4a6123e 100644 --- a/backend/src/matchMap.ts +++ b/backend/src/matchMap.ts @@ -10,6 +10,7 @@ import { TTeamSides, } from '../../common'; import * as commands from './commands'; +import { db } from './database'; import * as Events from './events'; import { colors, formatMapName } from './gameServer'; import * as Match from './match'; @@ -22,6 +23,7 @@ export const create = ( ): IMatchMap => { return { knifeForSide: knifeForSide, + knifeWinner: null, knifeRestart: { teamA: false, teamB: false, @@ -85,21 +87,21 @@ export const periodicJob = async (match: Match.Match, matchMap: IMatchMap) => { const getAvailableCommandsEnums = (state: TMatchMapSate): commands.TCommand[] => { switch (state) { case 'AFTER_KNIFE': - return ['RESTART', 'CT', 'T', 'STAY', 'SWITCH']; + return ['RESTART', 'CT', 'T', 'STAY', 'SWITCH', 'ADMIN']; case 'FINISHED': return []; case 'IN_PROGRESS': - return ['PAUSE', 'TACTICAL']; + return ['PAUSE', 'TACTICAL', 'ADMIN']; case 'KNIFE': - return ['RESTART']; + return ['RESTART', 'ADMIN']; case 'MAP_CHANGE': return []; case 'PAUSED': - return ['READY', 'UNREADY']; + return ['READY', 'UNREADY', 'ADMIN']; case 'PENDING': return []; case 'WARMUP': - return ['READY', 'UNREADY']; + return ['READY', 'UNREADY', 'ADMIN']; } }; @@ -210,7 +212,7 @@ export const loadMap = async (match: Match.Match, matchMap: IMatchMap, useDefaul matchMap.knifeRestart.teamB = false; matchMap.score.teamA = 0; matchMap.score.teamB = 0; - matchMap.knifeWinner = undefined; + matchMap.knifeWinner = null; MatchService.scheduleSave(match); }; @@ -296,7 +298,7 @@ const startKnifeRound = async (match: Match.Match, matchMap: IMatchMap) => { matchMap.state = 'KNIFE'; matchMap.knifeRestart.teamA = false; matchMap.knifeRestart.teamB = false; - matchMap.knifeWinner = undefined; + matchMap.knifeWinner = null; MatchService.scheduleSave(match); match.log('Start knife round'); await Match.execRconCommands(match, 'knife'); @@ -643,3 +645,109 @@ export const update = async ( MatchService.scheduleSave(match); }; + +export type TDbMatchMap = { + matchId: string; + index: number; + name: string; + knifeForSide: number; + startAsCtTeam: string; + state: string; + knifeWinner: string | null; + readyTeamA: number; + readyTeamB: number; + knifeRestartTeamA: number; + knifeRestartTeamB: number; + scoreTeamA: number; + scoreTeamB: number; +}; + +const matchMapToDb = (matchId: string, matchMap: IMatchMap, index: number): TDbMatchMap => { + return { + matchId: matchId, + index: index, + name: matchMap.name, + knifeForSide: matchMap.knifeForSide ? 1 : 0, + startAsCtTeam: matchMap.startAsCtTeam, + state: matchMap.state, + knifeWinner: matchMap.knifeWinner, + readyTeamA: matchMap.readyTeams.teamA ? 1 : 0, + readyTeamB: matchMap.readyTeams.teamB ? 1 : 0, + knifeRestartTeamA: matchMap.knifeRestart.teamA ? 1 : 0, + knifeRestartTeamB: matchMap.knifeRestart.teamB ? 1 : 0, + scoreTeamA: matchMap.score.teamA, + scoreTeamB: matchMap.score.teamB, + }; +}; + +export const matchMapFromDb = (dbMatchMap: TDbMatchMap): IMatchMap => { + return { + name: dbMatchMap.name, + knifeForSide: !!dbMatchMap.knifeForSide, + startAsCtTeam: dbMatchMap.startAsCtTeam as TTeamAB, + state: dbMatchMap.state as TMatchMapSate, + knifeWinner: dbMatchMap.knifeWinner as TTeamAB | null, + readyTeams: { + teamA: !!dbMatchMap.readyTeamA, + teamB: !!dbMatchMap.readyTeamB, + }, + knifeRestart: { + teamA: !!dbMatchMap.knifeRestartTeamA, + teamB: !!dbMatchMap.knifeRestartTeamB, + }, + score: { + teamA: dbMatchMap.scoreTeamA, + teamB: dbMatchMap.scoreTeamB, + }, + overTimeEnabled: true, + overTimeMaxRounds: 6, + maxRounds: 30, + }; +}; + +export const saveMatchMapToDb = (matchId: string, matchMap: IMatchMap, index: number) => { + db.prepare( + `INSERT INTO matchMap ( + matchId, + "index", + name, + knifeForSide, + startAsCtTeam, + state, + knifeWinner, + readyTeamA, + readyTeamB, + knifeRestartTeamA, + knifeRestartTeamB, + scoreTeamA, + scoreTeamB + ) VALUES ( + :matchId, + :index, + :name, + :knifeForSide, + :startAsCtTeam, + :state, + :knifeWinner, + :readyTeamA, + :readyTeamB, + :knifeRestartTeamA, + :knifeRestartTeamB, + :scoreTeamA, + :scoreTeamB + ) ON CONFLICT (matchId, "index") DO UPDATE SET + name = :name, + knifeForSide = :knifeForSide, + startAsCtTeam = :startAsCtTeam, + state = :state, + knifeWinner = :knifeWinner, + readyTeamA = :readyTeamA, + readyTeamB = :readyTeamB, + knifeRestartTeamA = :knifeRestartTeamA, + knifeRestartTeamB = :knifeRestartTeamB, + scoreTeamA = :scoreTeamA, + scoreTeamB = :scoreTeamB + WHERE matchId = :matchId AND "index" = :index + ` + ).run(matchMapToDb(matchId, matchMap, index)); +}; diff --git a/backend/src/matchService.ts b/backend/src/matchService.ts index 1c45d17..ee718de 100644 --- a/backend/src/matchService.ts +++ b/backend/src/matchService.ts @@ -1,11 +1,10 @@ import { generate as shortUuid } from 'short-uuid'; -import { IGameServer, IMatch, IMatchCreateDto, IMatchResponse } from '../../common'; +import { IGameServer, IMatch, IMatchCreateDto } from '../../common'; import * as Events from './events'; import * as Match from './match'; -import * as Storage from './storage'; - -const STORAGE_PREFIX = 'match_'; -const STORAGE_SUFFIX = '.json'; +import { db } from './database'; +import { saveMatchMapToDb, TDbMatchMap } from './matchMap'; +import { savePlayerToDb, TDbMatchPlayer } from './player'; const matches: Map = new Map(); @@ -21,7 +20,7 @@ const matchesToSave: Set = new Set(); let timeout: NodeJS.Timeout; export const setup = async () => { - const matchesFromStorage = await getAllFromStorage(); + const matchesFromStorage = getAllMatchesFromDatabase(); // begin with recent matches so when there are multiple matches // with the same game server we recreate the correct (most recent) one @@ -54,6 +53,7 @@ const loadMatchFromStorage = async (matchData: IMatch, logMessageSuffix?: string matchData, `Load match from storage ${logMessageSuffix ?? ''}`.trim() ); + match.data.isLive = true; matches.set(match.data.id, match); await save(match.data); } finally { @@ -66,7 +66,8 @@ export const create = async (dto: IMatchCreateDto) => { try { const logSecret = shortUuid(); startingMatches.add(id); - const match = await Match.createFromCreateDto(dto, id, logSecret); + const match = await Match.createNew(dto, id, logSecret); + match.data.isLive = true; matches.set(match.data.id, match); await save(match.data); Events.onMatchCreate(match); @@ -108,45 +109,22 @@ export const get = (id: string) => { return matches.get(id); }; -export const getFromStorage = async (id: string) => { - const matchData: IMatch | undefined = await Storage.read(STORAGE_PREFIX + id + STORAGE_SUFFIX); - return matchData; -}; - export const getAllLive = () => { return Array.from(matches.values()).map((match) => match.data); }; -export const getAllFromStorage = async () => { - const matchesFromStorage = await Storage.list(STORAGE_PREFIX, STORAGE_SUFFIX); - - const matches: IMatch[] = []; - - for (let i = 0; i < matchesFromStorage.length; i++) { - const fileName = matchesFromStorage[i]!; - const matchData: IMatch | undefined = await Storage.read(fileName); - if (matchData && fileName === STORAGE_PREFIX + matchData.id + STORAGE_SUFFIX) { - matches.push(matchData); - } - } - - return matches; -}; - -export const getAll = async () => { +export const getAll = (): IMatch[] => { const live = getAllLive(); - const storage = await getAllFromStorage(); + const storage = getAllMatchesFromDatabase(); const notLive = storage.filter((match) => !live.find((m) => match.id === m.id)); - return { - live, - notLive, - }; + return [...live, ...notLive]; }; export const remove = async (id: string) => { const match = matches.get(id); if (match) { await Match.stop(match); + match.data.isLive = false; matches.delete(id); save(match.data); return true; @@ -156,7 +134,7 @@ export const remove = async (id: string) => { }; export const removeStopped = async (id: string) => { - const matchFromStorage = await getFromStorage(id); + const matchFromStorage = getMatchFromDatabase(id); if (!matchFromStorage) { return false; } @@ -170,7 +148,7 @@ export const revive = async (id: string) => { if (match) { return false; } - const matchFromStorage = await getFromStorage(id); + const matchFromStorage = getMatchFromDatabase(id); if (!matchFromStorage) { return false; } @@ -183,7 +161,11 @@ export const save = async (matchData: IMatch) => { const previousLastSavedAt = matchData.lastSavedAt; matchData.lastSavedAt = Date.now(); try { - await Storage.write(STORAGE_PREFIX + matchData.id + STORAGE_SUFFIX, matchData); + Match.saveMatchToDb(matchData); + matchData.matchMaps.forEach((matchMap, index) => + saveMatchMapToDb(matchData.id, matchMap, index) + ); + matchData.players.forEach((player) => savePlayerToDb(matchData.id, player)); } catch (err) { matchData.lastSavedAt = previousLastSavedAt; throw err; @@ -201,10 +183,7 @@ export const isStartingMatch = (id: string) => { return startingMatches.has(id); }; -export const hideRconPassword = ( - match: T, - isLoggedIn: boolean -): T => { +export const hideRconPassword = (match: IMatch, isLoggedIn: boolean): IMatch => { return { ...match, gameServer: { @@ -223,3 +202,45 @@ export const getLiveMatchesByGameServer = (gameServer: IGameServer) => { match.gameServer.ip === gameServer.ip && match.gameServer.port === gameServer.port ); }; + +export const getMatchFromDatabase = (id: string): IMatch | undefined => { + const matchRow = db + .prepare<{ id: string }, Match.TDbMatch>('SELECT * FROM match WHERE id = :id') + .get({ id: id }); + if (!matchRow) { + return undefined; + } + const matchMapRows = db + .prepare< + { matchId: string }, + TDbMatchMap + >('SELECT * FROM matchMap WHERE matchId = :matchId') + .all({ matchId: id }); + const matchPlayerRows = db + .prepare< + { matchId: string }, + TDbMatchPlayer + >('SELECT * FROM matchPlayer WHERE matchId = :matchId') + .all({ matchId: id }); + return Match.matchFromDb(matchRow, matchMapRows, matchPlayerRows); +}; + +export const getAllMatchesFromDatabase = () => { + const matchRows = db.prepare<[], Match.TDbMatch>('SELECT * FROM match').all(); + const matchMapRows = db.prepare<[], TDbMatchMap>('SELECT * FROM matchMap').all(); + const matchPlayerRows = db.prepare<[], TDbMatchPlayer>('SELECT * FROM matchPlayer').all(); + + const matches: IMatch[] = []; + + for (let i = 0; i < matchRows.length; i++) { + const matchRow = matchRows[i] as any; + const match = Match.matchFromDb( + matchRow, + matchMapRows.filter((row: any) => row.matchId === matchRow.id), + matchPlayerRows.filter((row: any) => row.matchId === matchRow.id) + ); + matches.push(match); + } + + return matches; +}; diff --git a/backend/src/matchesController.ts b/backend/src/matchesController.ts index f05945f..04f1e40 100644 --- a/backend/src/matchesController.ts +++ b/backend/src/matchesController.ts @@ -12,16 +12,9 @@ import { Security, SuccessResponse, } from '@tsoa/runtime'; -import { - Event, - IMatch, - IMatchCreateDto, - IMatchMapUpdateDto, - IMatchResponse, - IMatchUpdateDto, -} from '../../common'; +import { Event, IMatch, IMatchCreateDto, IMatchMapUpdateDto, IMatchUpdateDto } from '../../common'; import { ExpressRequest, IAuthResponse, IAuthResponseOptional } from './auth'; -import * as Events from './events'; +import { getLatestEventsFromDatabase } from './events'; import * as Match from './match'; import * as MatchMap from './matchMap'; import * as MatchService from './matchService'; @@ -81,10 +74,11 @@ export class MatchesController extends Controller { @Query('state') state?: string[], @Query('passthrough') passthrough?: string[], @Query('isStopped') isStopped?: boolean, - @Query('isLive') isLive?: boolean - ): Promise { + @Query('isLive') isLive?: boolean, + @Query('needsAttention') needsAttention?: boolean + ): Promise { const live = MatchService.getAllLive(); - const storage = isLive === true ? [] : await MatchService.getAllFromStorage(); + const storage = isLive === true ? [] : MatchService.getAllMatchesFromDatabase(); const notLive = storage.filter((match) => !live.find((m) => match.id === m.id)); return [...live, ...notLive] .map((m) => ({ ...m, isLive: !!live.find((l) => l.id === m.id) })) @@ -96,6 +90,11 @@ export class MatchesController extends Controller { ) .filter((m) => isStopped === undefined || m.isStopped === isStopped) .filter((m) => isLive === undefined || m.isLive === isLive) + .filter( + (m) => + needsAttention === undefined || + needsAttention === (m.needsAttentionSince !== null) + ) .map((m) => MatchService.hideRconPassword(m, req.user.type === 'GLOBAL')); } @@ -106,20 +105,18 @@ export class MatchesController extends Controller { async getMatch( id: string, @Request() req: ExpressRequest - ): Promise { + ): Promise { const match = MatchService.get(id); if (match) { return { ...MatchService.hideRconPassword(match.data, req.user.type === 'GLOBAL'), - isLive: true, }; } - const matchFromStorage = await MatchService.getFromStorage(id); + const matchFromStorage = MatchService.getMatchFromDatabase(id); if (matchFromStorage) { return { ...MatchService.hideRconPassword(matchFromStorage, req.user.type === 'GLOBAL'), - isLive: false, }; } @@ -127,20 +124,12 @@ export class MatchesController extends Controller { return; } - /** - * Get the last 1000 log lines from a specific match. - */ - @Get('{id}/logs') - async getLogs(id: string, @Request() req: ExpressRequest): Promise { - return await Match.getLogsTail(id); - } - /** * Get the last 1000 events from a specific match. */ @Get('{id}/events') async getEvents(id: string, @Request() req: ExpressRequest): Promise { - return await Events.getEventsTail(id); + return getLatestEventsFromDatabase(id, 1000); } /** @@ -200,14 +189,20 @@ export class MatchesController extends Controller { if (match.data.gameServer.hideRconPassword) { checkRconCommands(requestBody.rconCommands, req.user.type === 'GLOBAL'); } - await Match.update(match, requestBody); + try { + await Match.update(match, requestBody); + } finally { + MatchService.scheduleSave(match); + } } else if (requestBody.gameServer) { // for offline matches only allow to update game server to get match running again - const offlineMatch = await MatchService.getFromStorage(id); + const offlineMatch = MatchService.getMatchFromDatabase(id); if (offlineMatch) { - const hideRconPassword = offlineMatch.gameServer.hideRconPassword; - offlineMatch.gameServer = requestBody.gameServer; - offlineMatch.gameServer.hideRconPassword = hideRconPassword; + const gameServer = { + ...requestBody.gameServer, + hideRconPassword: offlineMatch.gameServer.hideRconPassword, + }; + offlineMatch.gameServer = gameServer; await MatchService.save(offlineMatch); } } else { diff --git a/backend/src/migrations/01.ts b/backend/src/migrations/01.ts new file mode 100644 index 0000000..78e63b8 --- /dev/null +++ b/backend/src/migrations/01.ts @@ -0,0 +1,436 @@ +import { db } from '../database'; +import fs from 'node:fs'; +import path from 'node:path'; + +const STORAGE_FOLDER = process.env['TMT_STORAGE_FOLDER'] || 'storage'; + +export const migration01 = () => { + fs.mkdirSync(path.join(STORAGE_FOLDER, 'migrated'), { recursive: true }); + migrateMatches(); + migrateManagedGameServer(); + migrateEvents(); + migrateLogs(); +}; + +const migrateMatches = () => { + db.prepare( + `CREATE TABLE IF NOT EXISTS match ( + id TEXT PRIMARY KEY, + state TEXT NOT NULL, + passthrough TEXT, + mapPool TEXT NOT NULL, + teamAPassthrough TEXT, + teamAName TEXT NOT NULL, + teamAAdvantage INTEGER NOT NULL, + teamAPlayerSteamIds64 TEXT NOT NULL, + teamBPassthrough TEXT, + teamBName TEXT NOT NULL, + teamBAdvantage INTEGER NOT NULL, + teamBPlayerSteamIds64 TEXT NOT NULL, + electionSteps TEXT NOT NULL, + gameServerIp TEXT NOT NULL, + gameServerPort INTEGER NOT NULL, + gameServerRconPassword TEXT NOT NULL, + gameServerHideRconPassword INTEGER NOT NULL, + logSecret TEXT NOT NULL, + currentMap INTEGER NOT NULL, + webhookUrl TEXT, + webhookHeaders TEXT NOT NULL, + rconCommandsInit TEXT NOT NULL, + rconCommandsKnife TEXT NOT NULL, + rconCommandsMatch TEXT NOT NULL, + rconCommandsEnd TEXT NOT NULL, + canClinch INTEGER NOT NULL, + matchEndAction TEXT NOT NULL, + tmtSecret TEXT NOT NULL, + isStopped INTEGER NOT NULL, + tmtLogAddress TEXT, + createdAt INTEGER NOT NULL, + lastSavedAt INTEGER NOT NULL, + mode TEXT NOT NULL, + needsAttentionSince INTEGER + ) STRICT` + ).run(); + const insertMatchStatement = db.prepare(`INSERT INTO match ( + id, + state, + passthrough, + mapPool, + teamAPassthrough, + teamAName, + teamAAdvantage, + teamAPlayerSteamIds64, + teamBPassthrough, + teamBName, + teamBAdvantage, + teamBPlayerSteamIds64, + electionSteps, + gameServerIp, + gameServerPort, + gameServerRconPassword, + gameServerHideRconPassword, + logSecret, + currentMap, + webhookUrl, + webhookHeaders, + rconCommandsInit, + rconCommandsKnife, + rconCommandsMatch, + rconCommandsEnd, + canClinch, + matchEndAction, + tmtSecret, + isStopped, + tmtLogAddress, + createdAt, + lastSavedAt, + mode, + needsAttentionSince + ) VALUES ( + :id, + :state, + :passthrough, + :mapPool, + :teamAPassthrough, + :teamAName, + :teamAAdvantage, + :teamAPlayerSteamIds64, + :teamBPassthrough, + :teamBName, + :teamBAdvantage, + :teamBPlayerSteamIds64, + :electionSteps, + :gameServerIp, + :gameServerPort, + :gameServerRconPassword, + :gameServerHideRconPassword, + :logSecret, + :currentMap, + :webhookUrl, + :webhookHeaders, + :rconCommandsInit, + :rconCommandsKnife, + :rconCommandsMatch, + :rconCommandsEnd, + :canClinch, + :matchEndAction, + :tmtSecret, + :isStopped, + :tmtLogAddress, + :createdAt, + :lastSavedAt, + :mode, + :needsAttentionSince + )`); + + db.prepare( + `CREATE TABLE IF NOT EXISTS matchMap ( + matchId TEXT NOT NULL, + "index" INTEGER NOT NULL, + name TEXT NOT NULL, + knifeForSide INTEGER NOT NULL, + startAsCtTeam TEXT NOT NULL, + state TEXT NOT NULL, + knifeWinner TEXT, + readyTeamA INTEGER NOT NULL, + readyTeamB INTEGER NOT NULL, + knifeRestartTeamA INTEGER NOT NULL, + knifeRestartTeamB INTEGER NOT NULL, + scoreTeamA INTEGER NOT NULL, + scoreTeamB INTEGER NOT NULL, + PRIMARY KEY (matchId, "index") + FOREIGN KEY (matchId) REFERENCES match (id) ON UPDATE CASCADE ON DELETE CASCADE + ) STRICT` + ).run(); + const insertMatchMapStatement = db.prepare(`INSERT INTO matchMap ( + matchId, + "index", + name, + knifeForSide, + startAsCtTeam, + state, + knifeWinner, + readyTeamA, + readyTeamB, + knifeRestartTeamA, + knifeRestartTeamB, + scoreTeamA, + scoreTeamB + ) VALUES ( + :matchId, + :index, + :name, + :knifeForSide, + :startAsCtTeam, + :state, + :knifeWinner, + :readyTeamA, + :readyTeamB, + :knifeRestartTeamA, + :knifeRestartTeamB, + :scoreTeamA, + :scoreTeamB + )`); + + db.prepare( + `CREATE TABLE IF NOT EXISTS matchPlayer ( + matchId TEXT NOT NULL, + steamId64 TEXT NOT NULL, + name TEXT NOT NULL, + team TEXT, + side TEXT, + online INTEGER, + PRIMARY KEY (matchId, steamId64) + FOREIGN KEY (matchId) REFERENCES match (id) ON UPDATE CASCADE ON DELETE CASCADE + ) STRICT` + ).run(); + const insertMatchPlayerStatement = db.prepare(`INSERT INTO matchPlayer ( + matchId, + steamId64, + name, + team, + side, + online + ) VALUES ( + :matchId, + :steamId64, + :name, + :team, + :side, + :online + )`); + + const matchFiles = fs + .readdirSync(path.join(STORAGE_FOLDER)) + .filter((fileName) => fileName.startsWith('match_') && fileName.endsWith('.json')); + matchFiles.forEach((matchFile) => { + try { + const match = JSON.parse( + fs.readFileSync(path.join(STORAGE_FOLDER, matchFile), { encoding: 'utf-8' }) + ); + const params = { + id: match.id, + state: match.state, + passthrough: match.passthrough ?? null, + mapPool: JSON.stringify(match.mapPool), + teamAPassthrough: match.teamA.passthrough ?? null, + teamAName: match.teamA.name, + teamAAdvantage: match.teamA.advantage, + teamAPlayerSteamIds64: JSON.stringify(match.teamA.playerSteamIds64 ?? []), + teamBPassthrough: match.teamB.passthrough ?? null, + teamBName: match.teamB.name, + teamBAdvantage: match.teamB.advantage, + teamBPlayerSteamIds64: JSON.stringify(match.teamB.playerSteamIds64 ?? []), + electionSteps: JSON.stringify(match.electionSteps), + gameServerIp: match.gameServer.ip, + gameServerPort: match.gameServer.port, + gameServerRconPassword: match.gameServer.rconPassword, + gameServerHideRconPassword: match.gameServer.hideRconPassword === true ? 1 : 0, + logSecret: match.logSecret, + currentMap: match.currentMap, + webhookUrl: match.webhookUrl, + webhookHeaders: JSON.stringify(match.webhookHeaders ?? {}), + rconCommandsInit: JSON.stringify(match.rconCommands.init), + rconCommandsKnife: JSON.stringify(match.rconCommands.knife), + rconCommandsMatch: JSON.stringify(match.rconCommands.match), + rconCommandsEnd: JSON.stringify(match.rconCommands.end), + canClinch: match.canClinch ? 1 : 0, + matchEndAction: match.matchEndAction, + tmtSecret: match.tmtSecret, + isStopped: match.isStopped ? 1 : 0, + tmtLogAddress: match.tmtLogAddress ?? null, + createdAt: match.createdAt, + lastSavedAt: match.lastSavedAt, + mode: match.mode, + needsAttentionSince: null, + }; + insertMatchStatement.run(params); + + (match.matchMaps as any[]).forEach((matchMap, index) => { + const params = { + matchId: match.id, + index: index, + name: matchMap.name, + knifeForSide: matchMap.knifeForSide ? 1 : 0, + startAsCtTeam: matchMap.startAsCtTeam, + state: matchMap.state, + knifeWinner: matchMap.knifeWinner ?? null, + readyTeamA: matchMap.readyTeams.teamA ? 1 : 0, + readyTeamB: matchMap.readyTeams.teamB ? 1 : 0, + knifeRestartTeamA: matchMap.knifeRestart.teamA ? 1 : 0, + knifeRestartTeamB: matchMap.knifeRestart.teamB ? 1 : 0, + scoreTeamA: matchMap.score.teamA, + scoreTeamB: matchMap.score.teamB, + }; + insertMatchMapStatement.run(params); + }); + + (match.players as any[]).forEach((player) => { + const params = { + matchId: match.id, + steamId64: player.steamId64, + name: player.name, + team: player.team ?? null, + side: player.side ?? null, + online: player.online === true ? 1 : player.online === false ? 0 : null, + }; + insertMatchPlayerStatement.run(params); + }); + + fs.renameSync( + path.join(STORAGE_FOLDER, matchFile), + path.join(STORAGE_FOLDER, 'migrated', matchFile) + ); + console.log(`migrated match ${match.id}`); + } catch (err) { + console.error(`Could not migrate match ${matchFile}: ${err}`); + console.error(err); + } + }); +}; + +const migrateManagedGameServer = () => { + db.prepare( + `CREATE TABLE IF NOT EXISTS managedGameServer ( + ip TEXT NOT NULL, + port INTEGER NOT NULL, + rconPassword TEXT NOT NULL, + canBeUsed INTEGER NOT NULL, + usedBy TEXT, + PRIMARY KEY (ip, port) + ) STRICT` + ).run(); + + const insert = db.prepare( + `INSERT INTO managedGameServer ( + ip, + port, + rconPassword, + canBeUsed, + usedBy + ) VALUES ( + :ip, + :port, + :rconPassword, + :canBeUsed, + :usedBy + )` + ); + + if (!fs.existsSync(path.join(STORAGE_FOLDER, 'managed_game_servers.json'))) { + return; + } + const managedGameServers = JSON.parse( + fs.readFileSync(path.join(STORAGE_FOLDER, 'managed_game_servers.json'), { + encoding: 'utf-8', + }) + ) as any[]; + managedGameServers.forEach((managedGameServer, i) => { + try { + const params = { + ip: managedGameServer.ip, + port: managedGameServer.port, + rconPassword: managedGameServer.rconPassword, + canBeUsed: managedGameServer.canBeUsed ? 1 : 0, + usedBy: managedGameServer.usedBy, + }; + insert.run(params); + console.log( + `migrated managed gameserver ${managedGameServer.ip}:${managedGameServer.port}` + ); + } catch (err) { + console.error(`Could not migrate managed game server at index ${i}: ${err}`); + console.error(err); + } + }); + fs.renameSync( + path.join(STORAGE_FOLDER, 'managed_game_servers.json'), + path.join(STORAGE_FOLDER, 'migrated', 'managed_game_servers.json') + ); +}; + +const migrateEvents = () => { + db.prepare( + `CREATE TABLE IF NOT EXISTS event ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL, + matchId TEXT NOT NULL, + matchPassthrough TEXT, + type TEXT NOT NULL, + payload TEXT NOT NULL, + FOREIGN KEY (matchId) REFERENCES match (id) ON UPDATE CASCADE ON DELETE CASCADE + ) STRICT` + ).run(); + const insertStatement = db.prepare( + `INSERT INTO event ( + timestamp, + matchId, + matchPassthrough, + type, + payload + ) VALUEs ( + :timestamp, + :matchId, + :matchPassthrough, + :type, + :payload + )` + ); + + const eventsFiles = fs + .readdirSync(path.join(STORAGE_FOLDER)) + .filter((fileName) => fileName.startsWith('events_') && fileName.endsWith('.jsonl')); + eventsFiles.forEach((eventsFile) => { + const matchId = eventsFile.substring(7, eventsFile.length - 6); + try { + const events = fs + .readFileSync(path.join(STORAGE_FOLDER, eventsFile), { encoding: 'utf-8' }) + .trim() + .split('\n') + .map((line) => JSON.parse(line)); + events.forEach((event) => { + const payload = { ...event }; + delete payload.timestamp; + delete payload.matchId; + delete payload.matchPassthrough; + delete payload.type; + const params = { + timestamp: event.timestamp, + matchId: event.matchId, + matchPassthrough: event.matchPassthrough, + type: event.type, + payload: JSON.stringify(payload), + }; + insertStatement.run(params); + }); + console.log(`Migrated events from match ${matchId}`); + fs.renameSync( + path.join(STORAGE_FOLDER, eventsFile), + path.join(STORAGE_FOLDER, 'migrated', eventsFile) + ); + } catch (err: any) { + if (err.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') { + console.log(`Skip events from match ${matchId} (match does not exist).`); + fs.renameSync( + path.join(STORAGE_FOLDER, eventsFile), + path.join(STORAGE_FOLDER, 'migrated', eventsFile) + ); + } else { + console.error(`Could not migrate events from file ${eventsFile}: ${err}`); + console.error(err); + } + } + }); +}; + +const migrateLogs = () => { + const logsFiles = fs + .readdirSync(path.join(STORAGE_FOLDER)) + .filter((fileName) => fileName.startsWith('logs_') && fileName.endsWith('.jsonl')); + logsFiles.forEach((logsFile) => { + fs.renameSync( + path.join(STORAGE_FOLDER, logsFile), + path.join(STORAGE_FOLDER, 'migrated', logsFile) + ); + }); +}; diff --git a/backend/src/player.ts b/backend/src/player.ts index 9116123..a9daabf 100644 --- a/backend/src/player.ts +++ b/backend/src/player.ts @@ -1,6 +1,7 @@ import SteamID from 'steamid'; import { IPlayer, TTeamAB, TTeamSides, TTeamString } from '../../common'; import * as Match from './match'; +import { db } from './database'; export const create = (match: Match.Match, steamId: string, name: string): IPlayer => { const steamId64 = getSteamID64(steamId); @@ -8,6 +9,8 @@ export const create = (match: Match.Match, steamId: string, name: string): IPlay name: name, steamId64: steamId64, team: getForcedTeam(match, steamId64), + side: null, + online: null, }; }; @@ -15,13 +18,13 @@ export const getSteamID64 = (steamId: string) => { return new SteamID(steamId).getSteamID64(); }; -export const getForcedTeam = (match: Match.Match, steamId64: string): TTeamAB | undefined => { +export const getForcedTeam = (match: Match.Match, steamId64: string): TTeamAB | null => { const isTeamA = match.data.teamA.playerSteamIds64?.includes(steamId64); const isTeamB = match.data.teamB.playerSteamIds64?.includes(steamId64); if (isTeamA === isTeamB) { // either: configured for no teams // or: configured for both teams - return undefined; + return null; } return isTeamA ? 'TEAM_A' : 'TEAM_B'; }; @@ -53,3 +56,61 @@ export const getSideFromTeamString = (teamString: TTeamString): TTeamSides | nul return null; } }; + +export type TDbMatchPlayer = { + matchId: string; + steamId64: string; + name: string; + team: string | null; + side: string | null; + online: number | null; +}; + +export const matchPlayerToDb = (matchId: string, player: IPlayer): TDbMatchPlayer => { + return { + matchId: matchId, + steamId64: player.steamId64, + name: player.name, + team: player.team, + side: player.side, + online: player.online ? 1 : 0, + }; +}; + +export const matchPlayerFromDb = (dbMatchPlayer: TDbMatchPlayer): IPlayer => { + return { + steamId64: dbMatchPlayer.steamId64, + name: dbMatchPlayer.name, + team: dbMatchPlayer.team as TTeamAB | null, + side: dbMatchPlayer.side as TTeamSides | null, + online: dbMatchPlayer.online === null ? null : !!dbMatchPlayer.online, + }; +}; + +export const savePlayerToDb = (matchId: string, player: IPlayer) => { + db.prepare( + `INSERT INTO matchPlayer ( + matchId, + steamId64, + name, + team, + side, + online + ) VALUES ( + :matchId, + :steamId64, + :name, + :team, + :side, + :online + ) ON CONFLICT (matchId, steamId64) DO UPDATE SET + matchId = :matchId, + steamId64 = :steamId64, + name = :name, + team = :team, + side = :side, + online = :online + WHERE matchId = :matchId AND steamId64 = :steamId64 + ` + ).run(matchPlayerToDb(matchId, player)); +}; diff --git a/backend/src/presets.ts b/backend/src/presets.ts index 7be55ff..d6b42a8 100644 --- a/backend/src/presets.ts +++ b/backend/src/presets.ts @@ -87,7 +87,7 @@ const DEFAULT_PRESETS: IPreset[] = [ 'mp_give_player_c4 1; mp_startmoney 800; mp_ct_default_secondary "weapon_hkp2000"; mp_t_default_secondary "weapon_glock"', 'mp_overtime_enable 1', 'say > MATCH CONFIG LOADED <', - 'say > HF & LG - %TMT_MAP_NUMBER%. map: %TMT_MAP_NAME% <', + 'say > HF & GL - %TMT_MAP_NUMBER%. map: %TMT_MAP_NAME% <', ], end: ['say > MATCH END RCON LOADED <'], }, @@ -147,7 +147,7 @@ const DEFAULT_PRESETS: IPreset[] = [ 'mp_give_player_c4 1; mp_startmoney 800; mp_ct_default_secondary "weapon_hkp2000"; mp_t_default_secondary "weapon_glock"', 'mp_overtime_enable 1', 'say > MATCH CONFIG LOADED <', - 'say > HF & LG - %TMT_MAP_NUMBER%. map: %TMT_MAP_NAME% <', + 'say > HF & GL - %TMT_MAP_NUMBER%. map: %TMT_MAP_NAME% <', ], end: ['say > MATCH END RCON LOADED <'], }, diff --git a/backend/src/rcon-client/rcon.ts b/backend/src/rcon-client/rcon.ts index edf9aff..3fe4d96 100644 --- a/backend/src/rcon-client/rcon.ts +++ b/backend/src/rcon-client/rcon.ts @@ -118,7 +118,7 @@ export class Rcon { this.sendQueue.pause(); this.socket.destroy(); this.socket = null; - throw new Error('Authentication failed'); + throw new Error('Rcon authentication failed'); } this.authenticated = true; diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 08d6440..9a58ab1 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -45,10 +45,14 @@ const models: TsoaRoute.Models = { ITeam: { dataType: 'refObject', properties: { - passthrough: { dataType: 'string' }, + passthrough: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + required: true, + }, name: { dataType: 'string', required: true }, advantage: { dataType: 'double', required: true }, - playerSteamIds64: { dataType: 'array', array: { dataType: 'string' } }, + playerSteamIds64: { dataType: 'array', array: { dataType: 'string' }, required: true }, }, additionalProperties: false, }, @@ -255,12 +259,24 @@ const models: TsoaRoute.Models = { dataType: 'refObject', properties: { state: { ref: 'TElectionState', required: true }, - teamX: { ref: 'TTeamAB' }, - teamY: { ref: 'TTeamAB' }, + teamX: { + dataType: 'union', + subSchemas: [{ ref: 'TTeamAB' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + teamY: { + dataType: 'union', + subSchemas: [{ ref: 'TTeamAB' }, { dataType: 'enum', enums: [null] }], + required: true, + }, remainingMaps: { dataType: 'array', array: { dataType: 'string' }, required: true }, currentStep: { dataType: 'double', required: true }, currentSubStep: { ref: 'TStep', required: true }, - currentStepMap: { dataType: 'string' }, + currentStepMap: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + required: true, + }, currentAgree: { dataType: 'nestedObjectLiteral', nestedProperties: { @@ -295,7 +311,7 @@ const models: TsoaRoute.Models = { ip: { dataType: 'string', required: true }, port: { dataType: 'double', required: true }, rconPassword: { dataType: 'string', required: true }, - hideRconPassword: { dataType: 'boolean' }, + hideRconPassword: { dataType: 'boolean', required: true }, }, additionalProperties: false, }, @@ -325,7 +341,11 @@ const models: TsoaRoute.Models = { knifeForSide: { dataType: 'boolean', required: true }, startAsCtTeam: { ref: 'TTeamAB', required: true }, state: { ref: 'TMatchMapSate', required: true }, - knifeWinner: { ref: 'TTeamAB' }, + knifeWinner: { + dataType: 'union', + subSchemas: [{ ref: 'TTeamAB' }, { dataType: 'enum', enums: [null] }], + required: true, + }, readyTeams: { dataType: 'nestedObjectLiteral', nestedProperties: { @@ -370,64 +390,6 @@ const models: TsoaRoute.Models = { }, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - TLogType: { - dataType: 'refAlias', - type: { - dataType: 'union', - subSchemas: [ - { dataType: 'enum', enums: ['CHAT'] }, - { dataType: 'enum', enums: ['SYSTEM'] }, - ], - validators: {}, - }, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - ILogChat: { - dataType: 'refObject', - properties: { - type: { dataType: 'enum', enums: ['CHAT'], required: true }, - timestamp: { dataType: 'double', required: true }, - isTeamChat: { dataType: 'boolean', required: true }, - steamId64: { dataType: 'string', required: true }, - message: { dataType: 'string', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - TSystemLogCategory: { - dataType: 'refAlias', - type: { - dataType: 'union', - subSchemas: [ - { dataType: 'enum', enums: ['ERROR'] }, - { dataType: 'enum', enums: ['WARN'] }, - { dataType: 'enum', enums: ['INFO'] }, - { dataType: 'enum', enums: ['DEBUG'] }, - ], - validators: {}, - }, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - ILogSystem: { - dataType: 'refObject', - properties: { - type: { dataType: 'enum', enums: ['SYSTEM'], required: true }, - timestamp: { dataType: 'double', required: true }, - category: { ref: 'TSystemLogCategory', required: true }, - message: { dataType: 'string', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - TLogUnion: { - dataType: 'refAlias', - type: { - dataType: 'union', - subSchemas: [{ ref: 'ILogChat' }, { ref: 'ILogSystem' }], - validators: {}, - }, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa TTeamSides: { dataType: 'refAlias', type: { @@ -445,12 +407,21 @@ const models: TsoaRoute.Models = { properties: { steamId64: { dataType: 'string', required: true }, name: { dataType: 'string', required: true }, - team: { ref: 'TTeamAB' }, + team: { + dataType: 'union', + subSchemas: [{ ref: 'TTeamAB' }, { dataType: 'enum', enums: [null] }], + required: true, + }, side: { dataType: 'union', subSchemas: [{ ref: 'TTeamSides' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + online: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + required: true, }, - online: { dataType: 'boolean' }, }, additionalProperties: false, }, @@ -472,7 +443,11 @@ const models: TsoaRoute.Models = { properties: { id: { dataType: 'string', required: true }, state: { ref: 'TMatchState', required: true }, - passthrough: { dataType: 'string' }, + passthrough: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + required: true, + }, mapPool: { dataType: 'array', array: { dataType: 'string' }, required: true }, teamA: { ref: 'ITeam', required: true }, teamB: { ref: 'ITeam', required: true }, @@ -497,15 +472,9 @@ const models: TsoaRoute.Models = { required: true, }, webhookHeaders: { - dataType: 'union', - subSchemas: [ - { - dataType: 'nestedObjectLiteral', - nestedProperties: {}, - additionalProperties: { dataType: 'string' }, - }, - { dataType: 'enum', enums: [null] }, - ], + dataType: 'nestedObjectLiteral', + nestedProperties: {}, + additionalProperties: { dataType: 'string' }, required: true, }, rconCommands: { @@ -520,11 +489,6 @@ const models: TsoaRoute.Models = { }, canClinch: { dataType: 'boolean', required: true }, matchEndAction: { ref: 'TMatchEndAction', required: true }, - logs: { - dataType: 'array', - array: { dataType: 'refAlias', ref: 'TLogUnion' }, - required: true, - }, players: { dataType: 'array', array: { dataType: 'refObject', ref: 'IPlayer' }, @@ -533,10 +497,24 @@ const models: TsoaRoute.Models = { tmtSecret: { dataType: 'string', required: true }, isStopped: { dataType: 'boolean', required: true }, serverPassword: { dataType: 'string', required: true }, - tmtLogAddress: { dataType: 'string' }, + tmtLogAddress: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + required: true, + }, createdAt: { dataType: 'double', required: true }, - lastSavedAt: { dataType: 'double' }, + lastSavedAt: { + dataType: 'union', + subSchemas: [{ dataType: 'double' }, { dataType: 'enum', enums: [null] }], + required: true, + }, mode: { ref: 'TMatchMode', required: true }, + needsAttentionSince: { + dataType: 'union', + subSchemas: [{ dataType: 'double' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + isLive: { dataType: 'boolean', required: true }, }, additionalProperties: false, }, @@ -545,9 +523,21 @@ const models: TsoaRoute.Models = { dataType: 'refObject', properties: { name: { dataType: 'string', required: true }, - passthrough: { dataType: 'string' }, - advantage: { dataType: 'double' }, - playerSteamIds64: { dataType: 'array', array: { dataType: 'string' } }, + passthrough: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + }, + advantage: { + dataType: 'union', + subSchemas: [{ dataType: 'double' }, { dataType: 'enum', enums: [null] }], + }, + playerSteamIds64: { + dataType: 'union', + subSchemas: [ + { dataType: 'array', array: { dataType: 'string' } }, + { dataType: 'enum', enums: [null] }, + ], + }, }, additionalProperties: false, }, @@ -555,7 +545,10 @@ const models: TsoaRoute.Models = { IMatchCreateDto: { dataType: 'refObject', properties: { - passthrough: { dataType: 'string' }, + passthrough: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + }, mapPool: { dataType: 'array', array: { dataType: 'string' }, required: true }, teamA: { ref: 'ITeamCreateDto', required: true }, teamB: { ref: 'ITeamCreateDto', required: true }, @@ -568,70 +561,23 @@ const models: TsoaRoute.Models = { required: true, }, gameServer: { - dataType: 'union', - subSchemas: [{ ref: 'IGameServer' }, { dataType: 'enum', enums: [null] }], - required: true, - }, - webhookUrl: { - dataType: 'union', - subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], - }, - webhookHeaders: { dataType: 'union', subSchemas: [ { dataType: 'nestedObjectLiteral', - nestedProperties: {}, - additionalProperties: { dataType: 'string' }, + nestedProperties: { + rconPassword: { dataType: 'string', required: true }, + port: { dataType: 'double', required: true }, + ip: { dataType: 'string', required: true }, + }, }, { dataType: 'enum', enums: [null] }, ], - }, - rconCommands: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - end: { dataType: 'array', array: { dataType: 'string' } }, - match: { dataType: 'array', array: { dataType: 'string' } }, - knife: { dataType: 'array', array: { dataType: 'string' } }, - init: { dataType: 'array', array: { dataType: 'string' } }, - }, - }, - canClinch: { dataType: 'boolean' }, - matchEndAction: { ref: 'TMatchEndAction' }, - tmtLogAddress: { dataType: 'string' }, - mode: { ref: 'TMatchMode' }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - IMatchResponse: { - dataType: 'refObject', - properties: { - id: { dataType: 'string', required: true }, - state: { ref: 'TMatchState', required: true }, - passthrough: { dataType: 'string' }, - mapPool: { dataType: 'array', array: { dataType: 'string' }, required: true }, - teamA: { ref: 'ITeam', required: true }, - teamB: { ref: 'ITeam', required: true }, - electionSteps: { - dataType: 'array', - array: { dataType: 'refAlias', ref: 'IElectionStep' }, required: true, }, - election: { ref: 'IElection', required: true }, - gameServer: { ref: 'IGameServer', required: true }, - logSecret: { dataType: 'string', required: true }, - parseIncomingLogs: { dataType: 'boolean', required: true }, - matchMaps: { - dataType: 'array', - array: { dataType: 'refObject', ref: 'IMatchMap' }, - required: true, - }, - currentMap: { dataType: 'double', required: true }, webhookUrl: { dataType: 'union', subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], - required: true, }, webhookHeaders: { dataType: 'union', @@ -643,38 +589,56 @@ const models: TsoaRoute.Models = { }, { dataType: 'enum', enums: [null] }, ], - required: true, }, rconCommands: { dataType: 'nestedObjectLiteral', nestedProperties: { - end: { dataType: 'array', array: { dataType: 'string' }, required: true }, - match: { dataType: 'array', array: { dataType: 'string' }, required: true }, - knife: { dataType: 'array', array: { dataType: 'string' }, required: true }, - init: { dataType: 'array', array: { dataType: 'string' }, required: true }, + end: { + dataType: 'union', + subSchemas: [ + { dataType: 'array', array: { dataType: 'string' } }, + { dataType: 'enum', enums: [null] }, + ], + }, + match: { + dataType: 'union', + subSchemas: [ + { dataType: 'array', array: { dataType: 'string' } }, + { dataType: 'enum', enums: [null] }, + ], + }, + knife: { + dataType: 'union', + subSchemas: [ + { dataType: 'array', array: { dataType: 'string' } }, + { dataType: 'enum', enums: [null] }, + ], + }, + init: { + dataType: 'union', + subSchemas: [ + { dataType: 'array', array: { dataType: 'string' } }, + { dataType: 'enum', enums: [null] }, + ], + }, }, - required: true, }, - canClinch: { dataType: 'boolean', required: true }, - matchEndAction: { ref: 'TMatchEndAction', required: true }, - logs: { - dataType: 'array', - array: { dataType: 'refAlias', ref: 'TLogUnion' }, - required: true, + canClinch: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], }, - players: { - dataType: 'array', - array: { dataType: 'refObject', ref: 'IPlayer' }, - required: true, + matchEndAction: { + dataType: 'union', + subSchemas: [{ ref: 'TMatchEndAction' }, { dataType: 'enum', enums: [null] }], + }, + tmtLogAddress: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + }, + mode: { + dataType: 'union', + subSchemas: [{ ref: 'TMatchMode' }, { dataType: 'enum', enums: [null] }], }, - tmtSecret: { dataType: 'string', required: true }, - isStopped: { dataType: 'boolean', required: true }, - serverPassword: { dataType: 'string', required: true }, - tmtLogAddress: { dataType: 'string' }, - createdAt: { dataType: 'double', required: true }, - lastSavedAt: { dataType: 'double' }, - mode: { ref: 'TMatchMode', required: true }, - isLive: { dataType: 'boolean', required: true }, }, additionalProperties: false, }, @@ -740,7 +704,11 @@ const models: TsoaRoute.Models = { }, message: { dataType: 'string', required: true }, isTeamChat: { dataType: 'boolean', required: true }, - teamString: { ref: 'TTeamString' }, + teamString: { + dataType: 'union', + subSchemas: [{ ref: 'TTeamString' }, { dataType: 'enum', enums: [null] }], + required: true, + }, }, additionalProperties: false, }, @@ -929,7 +897,11 @@ const models: TsoaRoute.Models = { type: { dataType: 'enum', enums: ['ELECTION_MAP_STEP'], required: true }, mode: { ref: 'TMapMode', required: true }, mapName: { dataType: 'string', required: true }, - pickerTeam: { ref: 'ITeam' }, + pickerTeam: { + dataType: 'union', + subSchemas: [{ ref: 'ITeam' }, { dataType: 'enum', enums: [null] }], + required: true, + }, }, additionalProperties: false, }, @@ -960,10 +932,26 @@ const models: TsoaRoute.Models = { }, type: { dataType: 'enum', enums: ['ELECTION_SIDE_STEP'], required: true }, mode: { ref: 'TSideMode', required: true }, - pickerTeam: { ref: 'ITeam' }, - pickerSide: { ref: 'TTeamSides' }, - ctTeam: { ref: 'ITeam' }, - tTeam: { ref: 'ITeam' }, + pickerTeam: { + dataType: 'union', + subSchemas: [{ ref: 'ITeam' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + pickerSide: { + dataType: 'union', + subSchemas: [{ ref: 'TTeamSides' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + ctTeam: { + dataType: 'union', + subSchemas: [{ ref: 'ITeam' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + tTeam: { + dataType: 'union', + subSchemas: [{ ref: 'ITeam' }, { dataType: 'enum', enums: [null] }], + required: true, + }, }, additionalProperties: false, }, @@ -979,7 +967,7 @@ const models: TsoaRoute.Models = { required: true, }, type: { dataType: 'enum', enums: ['MATCH_CREATE'], required: true }, - match: { ref: 'IMatchResponse', required: true }, + match: { ref: 'IMatch', required: true }, }, additionalProperties: false, }, @@ -1060,7 +1048,14 @@ const models: TsoaRoute.Models = { subSchemas: [{ ref: 'IElectionStepAdd' }, { ref: 'IElectionStepSkip' }], }, }, - gameServer: { ref: 'IGameServer' }, + gameServer: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + rconPassword: { dataType: 'string', required: true }, + port: { dataType: 'double', required: true }, + ip: { dataType: 'string', required: true }, + }, + }, webhookUrl: { dataType: 'string' }, webhookHeaders: { dataType: 'nestedObjectLiteral', @@ -1087,14 +1082,42 @@ const models: TsoaRoute.Models = { }, tmtLogAddress: { dataType: 'string' }, mode: { ref: 'TMatchMode' }, - state: { ref: 'TMatchState' }, - logSecret: { dataType: 'string' }, - currentMap: { dataType: 'double' }, - _restartElection: { dataType: 'boolean' }, - _execRconCommandsInit: { dataType: 'boolean' }, - _execRconCommandsKnife: { dataType: 'boolean' }, - _execRconCommandsMatch: { dataType: 'boolean' }, - _execRconCommandsEnd: { dataType: 'boolean' }, + state: { + dataType: 'union', + subSchemas: [{ ref: 'TMatchState' }, { dataType: 'enum', enums: [null] }], + }, + logSecret: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + }, + currentMap: { + dataType: 'union', + subSchemas: [{ dataType: 'double' }, { dataType: 'enum', enums: [null] }], + }, + _restartElection: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, + _execRconCommandsInit: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, + _execRconCommandsKnife: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, + _execRconCommandsMatch: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, + _execRconCommandsEnd: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, + needsAttentionSince: { + dataType: 'union', + subSchemas: [{ dataType: 'double' }, { dataType: 'enum', enums: [null] }], + }, }, additionalProperties: false, }, @@ -1131,8 +1154,14 @@ const models: TsoaRoute.Models = { overTimeEnabled: { dataType: 'boolean' }, overTimeMaxRounds: { dataType: 'double' }, maxRounds: { dataType: 'double' }, - _refreshOvertimeAndMaxRoundsSettings: { dataType: 'boolean' }, - _switchTeamInternals: { dataType: 'boolean' }, + _refreshOvertimeAndMaxRoundsSettings: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, + _switchTeamInternals: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, }, additionalProperties: false, }, @@ -1143,7 +1172,6 @@ const models: TsoaRoute.Models = { ip: { dataType: 'string', required: true }, port: { dataType: 'double', required: true }, rconPassword: { dataType: 'string', required: true }, - hideRconPassword: { dataType: 'boolean' }, canBeUsed: { dataType: 'boolean', required: true }, usedBy: { dataType: 'union', @@ -1160,8 +1188,10 @@ const models: TsoaRoute.Models = { ip: { dataType: 'string', required: true }, port: { dataType: 'double', required: true }, rconPassword: { dataType: 'string', required: true }, - hideRconPassword: { dataType: 'boolean' }, - canBeUsed: { dataType: 'boolean' }, + canBeUsed: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, }, additionalProperties: false, }, @@ -1232,7 +1262,10 @@ const models: TsoaRoute.Models = { dataType: 'refObject', properties: { name: { dataType: 'string', required: true }, - isPublic: { dataType: 'boolean' }, + isPublic: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, data: { ref: 'IMatchCreateDto', required: true }, id: { dataType: 'string', required: true }, }, @@ -1243,7 +1276,10 @@ const models: TsoaRoute.Models = { dataType: 'refObject', properties: { name: { dataType: 'string', required: true }, - isPublic: { dataType: 'boolean' }, + isPublic: { + dataType: 'union', + subSchemas: [{ dataType: 'boolean' }, { dataType: 'enum', enums: [null] }], + }, data: { ref: 'IMatchCreateDto', required: true }, }, additionalProperties: false, @@ -1349,6 +1385,7 @@ export function RegisterRoutes(app: Router) { }, isStopped: { in: 'query', name: 'isStopped', dataType: 'boolean' }, isLive: { in: 'query', name: 'isLive', dataType: 'boolean' }, + needsAttention: { in: 'query', name: 'needsAttention', dataType: 'boolean' }, }; app.get( '/api/matches', @@ -1428,47 +1465,6 @@ export function RegisterRoutes(app: Router) { } ); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsMatchesController_getLogs: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - }; - app.get( - '/api/matches/:id/logs', - authenticateMiddleware([{ bearer_token: [] }]), - ...fetchMiddlewares(MatchesController), - ...fetchMiddlewares(MatchesController.prototype.getLogs), - - async function MatchesController_getLogs( - request: ExRequest, - response: ExResponse, - next: any - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsMatchesController_getLogs, - request, - response, - }); - - const controller = new MatchesController(); - - await templateService.apiHandler({ - methodName: 'getLogs', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - } - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const argsMatchesController_getEvents: Record = { id: { in: 'path', name: 'id', required: true, dataType: 'string' }, req: { in: 'request', name: 'req', required: true, dataType: 'object' }, diff --git a/backend/src/storage.ts b/backend/src/storage.ts index 6bebb23..49091b7 100644 --- a/backend/src/storage.ts +++ b/backend/src/storage.ts @@ -31,42 +31,3 @@ export const read: TRead = async (fileName: string, fallback?: T) => { return fallback; } }; - -export const appendLine = async (fileName: string, content: any) => { - try { - await fsp.appendFile(path.join(STORAGE_FOLDER, fileName), JSON.stringify(content) + '\n'); - } catch (err) { - console.warn(`Error storage appendLine ${fileName}: ${err}`); - } -}; - -export const readLines = async ( - fileName: string, - fallback: Array, - numberLastOfLines?: number -) => { - try { - const fullPath = path.join(STORAGE_FOLDER, fileName); - if (!fs.existsSync(fullPath) && fallback) { - throw 'file does not exist'; - } - const content = await fsp.readFile(fullPath, { encoding: 'utf8' }); - return content - .split('\n') - .filter((line) => line.trim().length > 0) - .map((line) => JSON.parse(line)) - .slice(-(numberLastOfLines ?? 0)); - } catch (err) { - console.warn(`Error storage readLines ${fileName}: ${err}. Use fallback.`); - return fallback; - } -}; - -/** - * Returns a list of all files in the storage folder which does match the given prefix and suffix. - * The returned file names still include the prefix and suffix. - */ -export const list = async (prefix: string, suffix: string) => { - const files = await fsp.readdir(STORAGE_FOLDER); - return files.filter((fileName) => fileName.startsWith(prefix) && fileName.endsWith(suffix)); -}; diff --git a/backend/src/team.ts b/backend/src/team.ts index e7f0881..16f4f0b 100644 --- a/backend/src/team.ts +++ b/backend/src/team.ts @@ -9,5 +9,7 @@ export const createFromCreateDto = (dto: ITeamCreateDto): ITeam => { return { ...dto, advantage: dto.advantage ?? 0, + passthrough: dto.passthrough ?? null, + playerSteamIds64: dto.playerSteamIds64 ?? [], }; }; diff --git a/backend/src/webSocket.ts b/backend/src/webSocket.ts index d229beb..4deedc3 100644 --- a/backend/src/webSocket.ts +++ b/backend/src/webSocket.ts @@ -1,109 +1,174 @@ import http from 'http'; import * as WebSocket from 'ws'; +import * as Auth from './auth'; +import * as MatchService from './matchService'; import { + AuthRequest, Event, - SubscribeMessage, - SubscribeSysMessage, - UnsubscribeMessage, - UnsubscribeSysMessage, - WebSocketMessage, + GetEventsRequest, + GetEventsResponse, + GetMatchesRequest, + GetMatchesResponse, + IMatch, + Message, + Payload, } from '../../common'; -import * as Auth from './auth'; +import { getLatestEventsFromDatabase } from './events'; -const WS_CLIENTS = new Map< - WebSocket, - { - ip?: string; - forwardedFor?: string | string[]; - userAgent?: string; - matches: Set; - hasSysSub: boolean; - } ->(); +type Client = { + ws: WebSocket; + ip?: string; + forwardedFor?: string | string[]; + userAgent?: string; + matches: Set; + isGlobalAdmin: boolean; +}; +const CLIENTS = new Map(); + +class FatalError extends Error {} + +class ResponseError extends Error {} export const setup = async (httpServer: http.Server) => { const wsServer = new WebSocket.Server({ server: httpServer, path: '/ws', }); - wsServer.on('connection', (ws: WebSocket, req) => { - WS_CLIENTS.set(ws, { + wsServer.on('connection', (webSocket: WebSocket, req) => { + CLIENTS.set(webSocket, { + ws: webSocket, ip: req.socket.remoteAddress, forwardedFor: req.headers['x-forwarded-for'], userAgent: req.headers['user-agent'], matches: new Set(), - hasSysSub: false, + isGlobalAdmin: false, }); - ws.on('message', (data) => onMessage(ws, data)); + webSocket.on('message', (data) => onMessage(webSocket, data)); - ws.on('close', () => WS_CLIENTS.delete(ws)); + webSocket.on('close', () => CLIENTS.delete(webSocket)); }); }; export const getClients = () => { - return [...WS_CLIENTS.values()]; + return [...CLIENTS.values()]; }; -const onMessage = (ws: WebSocket, data: WebSocket.RawData) => { - let msg: WebSocketMessage | undefined; +const onMessage = async (webSocket: WebSocket, data: WebSocket.RawData) => { + let msg: Message | undefined; + let c: Client | undefined; try { - msg = JSON.parse(data + ''); - } catch (err) {} - if (!msg) { - console.warn('Could not json parse webSocket data'); - return; - } - if (msg.type === 'SUBSCRIBE' || msg.type === 'SUBSCRIBE_SYS') { - subscribe(ws, msg); - } else if (msg.type === 'UNSUBSCRIBE' || msg.type === 'UNSUBSCRIBE_SYS') { - unsubscribe(ws, msg); - } else { - console.warn(`WebSocket type ${(msg as any).type} not implemented`); - } -}; + c = CLIENTS.get(webSocket); + if (!c) { + throw new FatalError('WebSocket message came from unknown client'); + } -const subscribe = async (ws: WebSocket, msg: SubscribeMessage | SubscribeSysMessage) => { - try { - const matchId = msg.type === 'SUBSCRIBE' ? msg.matchId : undefined; - const authResponse = await Auth.isAuthorized(msg.token, matchId); - if (!authResponse) { - console.warn(`Prevent subscribing: not authorized, payload: ${JSON.stringify(msg)}`); - return; + try { + msg = JSON.parse(data + ''); + } catch (err) {} + if (!msg) { + throw new FatalError('Could not json parse webSocket data'); } - const wsData = WS_CLIENTS.get(ws); - if (wsData) { - if (msg.type === 'SUBSCRIBE') { - wsData.matches.add(msg.matchId); - } else if (msg.type === 'SUBSCRIBE_SYS') { - wsData.hasSysSub = true; - } + + if (msg.type !== 'REQUEST') { + throw new FatalError(`WebSocket message type ${msg.type} is not supported`); + } + + if (!msg.payload) { + throw new FatalError(`WebSocket request message needs a payload`); + } + + if (!('request' in msg.payload)) { + throw new FatalError(`Property 'request' is required in a request payload`); + } + const request = msg.payload.request; + + if (request === 'AUTH') { + await auth(c, msg, msg.payload); + } else if (request === 'GET_MATCHES') { + getMatches(c, msg, msg.payload); + } else if (request === 'GET_EVENTS') { + getEvents(c, msg, msg.payload); + } else { + console.warn(`WebSocket request ${request} not implemented`); } } catch (err) { - console.error(`Subscribe error: ${err}`); + if (err instanceof FatalError || msg?.msgId === undefined || !c) { + console.warn(`Fatal error in handling webSocket message: ${err}`); + } else { + console.warn(`Error in handling webSocket message: ${err}`); + webSocket.send( + JSON.stringify({ + type: 'RESPONSE', + msgId: msg.msgId, + error: err + '', + } satisfies Message) + ); + } } }; -const unsubscribe = (ws: WebSocket, msg: UnsubscribeMessage | UnsubscribeSysMessage) => { - const wsData = WS_CLIENTS.get(ws); - if (!wsData) { - return; +const sendResponse = (c: Client, req: Message, payload: Payload) => { + c.ws.send( + JSON.stringify({ + type: 'RESPONSE', + msgId: req.msgId, + payload: payload, + } as Message) + ); +}; + +const auth = async (c: Client, req: Message, payload: AuthRequest) => { + const authResponse = Auth.isAuthorized(payload.token); + if (!authResponse) { + throw new ResponseError('Auth failed'); } + c.isGlobalAdmin = true; + sendResponse(c, req, { type: 'ACK' }); +}; + +const getMatches = (c: Client, req: Message, payload: GetMatchesRequest) => { + const allMatches = MatchService.getAll(); + + const matches: IMatch[] = []; + for (let i = 0; i < allMatches.length; i++) { + const match = allMatches[i]!; + const requestEntry = payload.matches?.find((m) => m.id === match.id); + const hasAccess = c.isGlobalAdmin || requestEntry?.secret === match.tmtSecret; + const wanted = requestEntry || !payload.matches; + if (hasAccess && wanted) { + matches.push(MatchService.hideRconPassword(match, c.isGlobalAdmin)); + if (!c.isGlobalAdmin) { + c.matches.add(match.id); + } + } + } + + const res: GetMatchesResponse = { + matches: matches, + }; + sendResponse(c, req, res); +}; - if (msg.type === 'UNSUBSCRIBE') { - wsData.matches.delete(msg.matchId); - } else if (msg.type === 'UNSUBSCRIBE_SYS') { - wsData.hasSysSub = false; +const getEvents = (c: Client, req: Message, payload: GetEventsRequest) => { + if (!c.isGlobalAdmin && !c.matches.has(payload.matchId)) { + throw new ResponseError('Not allowed'); } + const res: GetEventsResponse = { + events: getLatestEventsFromDatabase(payload.matchId), + }; + sendResponse(c, req, res); }; -export const publish = (msg: Event, isSystemEvent?: boolean) => { - WS_CLIENTS.forEach((wsData, ws) => { - if ( - (msg.matchId && wsData.matches.has(msg.matchId)) || - (wsData.hasSysSub && isSystemEvent) - ) { - ws.send(JSON.stringify(msg)); +export const publish = (msg: Event) => { + CLIENTS.forEach((c, ws) => { + if (c.isGlobalAdmin || c.matches.has(msg.matchId)) { + ws.send( + JSON.stringify({ + type: 'EVENT', + payload: msg, + } as Message) + ); } }); }; diff --git a/backend/swagger.json b/backend/swagger.json index dbb3d35..76cb313 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -17,6 +17,7 @@ "properties": { "passthrough": { "type": "string", + "nullable": true, "description": "Passthrough data to identify team in other systems.\nWill be present in every response/webhook." }, "name": { @@ -36,7 +37,7 @@ "description": "Steam ids of players in \"Steam ID 64\" format. Will be forced into this team." } }, - "required": ["name", "advantage"], + "required": ["passthrough", "name", "advantage", "playerSteamIds64"], "type": "object", "additionalProperties": false }, @@ -256,10 +257,20 @@ "$ref": "#/components/schemas/TElectionState" }, "teamX": { - "$ref": "#/components/schemas/TTeamAB" + "allOf": [ + { + "$ref": "#/components/schemas/TTeamAB" + } + ], + "nullable": true }, "teamY": { - "$ref": "#/components/schemas/TTeamAB" + "allOf": [ + { + "$ref": "#/components/schemas/TTeamAB" + } + ], + "nullable": true }, "remainingMaps": { "items": { @@ -279,6 +290,7 @@ }, "currentStepMap": { "type": "string", + "nullable": true, "description": "Current set map of the current selection step." }, "currentAgree": { @@ -312,9 +324,12 @@ }, "required": [ "state", + "teamX", + "teamY", "remainingMaps", "currentStep", "currentSubStep", + "currentStepMap", "currentAgree", "currentRestart" ], @@ -338,7 +353,7 @@ "description": "If plebs (client without an admin token) create a match the hideRconPassword attribute is set to true.\nThis will prevent executing rcon commands from the frontend by the (unauthorized) user." } }, - "required": ["ip", "port", "rconPassword"], + "required": ["ip", "port", "rconPassword", "hideRconPassword"], "type": "object", "additionalProperties": false }, @@ -373,7 +388,12 @@ "$ref": "#/components/schemas/TMatchMapSate" }, "knifeWinner": { - "$ref": "#/components/schemas/TTeamAB", + "allOf": [ + { + "$ref": "#/components/schemas/TTeamAB" + } + ], + "nullable": true, "description": "Winner of the knife round which is able to or already has picked a starting side." }, "readyTeams": { @@ -435,6 +455,7 @@ "knifeForSide", "startAsCtTeam", "state", + "knifeWinner", "readyTeams", "knifeRestart", "score", @@ -449,71 +470,6 @@ "type": "string", "enum": ["KICK_ALL", "QUIT_SERVER", "NONE"] }, - "TLogType": { - "type": "string", - "enum": ["CHAT", "SYSTEM"] - }, - "ILogChat": { - "properties": { - "type": { - "type": "string", - "enum": ["CHAT"], - "nullable": false - }, - "timestamp": { - "type": "number", - "format": "double" - }, - "isTeamChat": { - "type": "boolean" - }, - "steamId64": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "timestamp", "isTeamChat", "steamId64", "message"], - "type": "object", - "additionalProperties": false - }, - "TSystemLogCategory": { - "type": "string", - "enum": ["ERROR", "WARN", "INFO", "DEBUG"] - }, - "ILogSystem": { - "properties": { - "type": { - "type": "string", - "enum": ["SYSTEM"], - "nullable": false - }, - "timestamp": { - "type": "number", - "format": "double" - }, - "category": { - "$ref": "#/components/schemas/TSystemLogCategory" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "timestamp", "category", "message"], - "type": "object", - "additionalProperties": false - }, - "TLogUnion": { - "anyOf": [ - { - "$ref": "#/components/schemas/ILogChat" - }, - { - "$ref": "#/components/schemas/ILogSystem" - } - ] - }, "TTeamSides": { "type": "string", "enum": ["CT", "T"] @@ -530,7 +486,12 @@ "description": "Name." }, "team": { - "$ref": "#/components/schemas/TTeamAB", + "allOf": [ + { + "$ref": "#/components/schemas/TTeamAB" + } + ], + "nullable": true, "description": "Current team as they joined with `.team`.\nIf the player's steam id is in the team's `playerSteamIds64`\nthis cannot be changed and is always set to the team." }, "side": { @@ -544,10 +505,11 @@ }, "online": { "type": "boolean", + "nullable": true, "description": "Player currently on the game server (online)?" } }, - "required": ["steamId64", "name"], + "required": ["steamId64", "name", "team", "side", "online"], "type": "object", "additionalProperties": false }, @@ -567,6 +529,7 @@ }, "passthrough": { "type": "string", + "nullable": true, "description": "e.g. remote identifier, will be present in every response/webhook" }, "mapPool": { @@ -629,7 +592,6 @@ "type": "string" }, "type": "object", - "nullable": true, "description": "Additional headers that will be added to each webhook request" }, "rconCommands": { @@ -674,12 +636,6 @@ "$ref": "#/components/schemas/TMatchEndAction", "description": "defaults to NONE" }, - "logs": { - "items": { - "$ref": "#/components/schemas/TLogUnion" - }, - "type": "array" - }, "players": { "items": { "$ref": "#/components/schemas/IPlayer" @@ -700,6 +656,7 @@ }, "tmtLogAddress": { "type": "string", + "nullable": true, "description": "if set will be used to register the target logaddress for the game server" }, "createdAt": { @@ -710,16 +667,28 @@ "lastSavedAt": { "type": "number", "format": "double", + "nullable": true, "description": "Last time the match was saved to disk (unix time in milliseconds since midnight, January 1, 1970 UTC)" }, "mode": { "$ref": "#/components/schemas/TMatchMode", "description": "Match mode (single: stops when match is finished, loop: starts again after match is finished)" + }, + "needsAttentionSince": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Since when (unix time in milliseconds) does the match needs attention (set via ingame `.admin` command)." + }, + "isLive": { + "type": "boolean", + "description": "Match is currently supervised." } }, "required": [ "id", "state", + "passthrough", "mapPool", "teamA", "teamB", @@ -735,13 +704,16 @@ "rconCommands", "canClinch", "matchEndAction", - "logs", "players", "tmtSecret", "isStopped", "serverPassword", + "tmtLogAddress", "createdAt", - "mode" + "lastSavedAt", + "mode", + "needsAttentionSince", + "isLive" ], "type": "object", "additionalProperties": false @@ -754,11 +726,13 @@ }, "passthrough": { "type": "string", + "nullable": true, "description": "Passthrough data to identify team in other systems.\nWill be present in every response/webhook." }, "advantage": { "type": "number", "format": "double", + "nullable": true, "description": "Advantage in map wins, useful for double elimination tournament finals." }, "playerSteamIds64": { @@ -766,6 +740,7 @@ "type": "string" }, "type": "array", + "nullable": true, "description": "Steam ids of players in \"Steam ID 64\" format. Will be forced into this team." } }, @@ -777,6 +752,7 @@ "properties": { "passthrough": { "type": "string", + "nullable": true, "description": "e.g. remote identifier, will be present in every response/webhook" }, "mapPool": { @@ -806,142 +782,21 @@ "type": "array" }, "gameServer": { - "allOf": [ - { - "$ref": "#/components/schemas/IGameServer" - } - ], - "nullable": true - }, - "webhookUrl": { - "type": "string", - "nullable": true, - "description": "Send various events to this url (HTTP POST)" - }, - "webhookHeaders": { - "properties": {}, - "additionalProperties": { - "type": "string" - }, - "type": "object", - "nullable": true, - "description": "Additional headers that will be added to each webhook request" - }, - "rconCommands": { "properties": { - "end": { - "items": { - "type": "string" - }, - "type": "array", - "description": "executed after last match map" - }, - "match": { - "items": { - "type": "string" - }, - "type": "array", - "description": "executed before every match map start" + "rconPassword": { + "type": "string" }, - "knife": { - "items": { - "type": "string" - }, - "type": "array", - "description": "executed before every knife round" + "port": { + "type": "number", + "format": "double" }, - "init": { - "items": { - "type": "string" - }, - "type": "array", - "description": "executed exactly once on match init" + "ip": { + "type": "string" } }, - "type": "object" - }, - "canClinch": { - "type": "boolean", - "description": "defaults to true, means that possibly not all maps will be played if the winner is determined before" - }, - "matchEndAction": { - "$ref": "#/components/schemas/TMatchEndAction", - "description": "defaults to NONE" - }, - "tmtLogAddress": { - "type": "string", - "description": "if set will be used to register the target logaddress for the game server" - }, - "mode": { - "$ref": "#/components/schemas/TMatchMode", - "description": "Match mode (single: stops when match is finished, loop: starts again after match is finished)" - } - }, - "required": ["mapPool", "teamA", "teamB", "electionSteps", "gameServer"], - "type": "object", - "additionalProperties": false - }, - "IMatchResponse": { - "properties": { - "id": { - "type": "string", - "description": "tmt2 identifier for this match" - }, - "state": { - "$ref": "#/components/schemas/TMatchState" - }, - "passthrough": { - "type": "string", - "description": "e.g. remote identifier, will be present in every response/webhook" - }, - "mapPool": { - "items": { - "type": "string" - }, - "type": "array", - "description": "The maps the players can pick or ban.\nWill also be used if a map is chosen randomly.\nIf the map is fixed it will not be removed from the map pool.\nWorkshop maps are possible (contain numbers only).\nFriendly names for players when picking/banning maps can be added after a \"/\" delimiter and is advised for workshop maps.\n\nExample:\n```\n[\n \"de_ancient\",\n \"de_anubis/anubis\",\n \"de_inferno\",\n \"de_mirage\",\n \"de_nuke\",\n \"de_overpass\",\n \"de_vertigo\",\n \"3070923343/fy_pool_day\"\n]\n```" - }, - "teamA": { - "$ref": "#/components/schemas/ITeam", - "description": "Team A for this match.\nTeam A will always be Team A in responses and webhooks.\nNo matter on which side (CT/T) it is currently." - }, - "teamB": { - "$ref": "#/components/schemas/ITeam", - "description": "Team B for this match.\nTeam B will always be Team B in responses and webhooks.\nNo matter on which side (CT/T) it is currently." - }, - "electionSteps": { - "items": { - "$ref": "#/components/schemas/IElectionStep" - }, - "type": "array", - "description": "List of election steps to determine the played map(s)." - }, - "election": { - "$ref": "#/components/schemas/IElection", - "description": "Data for the election process." - }, - "gameServer": { - "$ref": "#/components/schemas/IGameServer" - }, - "logSecret": { - "type": "string", - "description": "Log secret that is given as part of the url to the CS2 server as a log receiver (logaddress_add_http)." - }, - "parseIncomingLogs": { - "type": "boolean", - "description": "Indicates if incoming logs from the CS2 server are parsed (otherwise they will be dropped without any action).\nWill be set to true if match is loaded from storage (after a short delay)." - }, - "matchMaps": { - "items": { - "$ref": "#/components/schemas/IMatchMap" - }, - "type": "array", - "description": "The maps which will be played. If match state is still ELECTION than this is not final." - }, - "currentMap": { - "type": "number", - "format": "double", - "description": "Index of the matchMaps array indicating the current map." + "required": ["rconPassword", "port", "ip"], + "type": "object", + "nullable": true }, "webhookUrl": { "type": "string", @@ -964,6 +819,7 @@ "type": "string" }, "type": "array", + "nullable": true, "description": "executed after last match map" }, "match": { @@ -971,6 +827,7 @@ "type": "string" }, "type": "array", + "nullable": true, "description": "executed before every match map start" }, "knife": { @@ -978,6 +835,7 @@ "type": "string" }, "type": "array", + "nullable": true, "description": "executed before every knife round" }, "init": { @@ -985,94 +843,42 @@ "type": "string" }, "type": "array", + "nullable": true, "description": "executed exactly once on match init" } }, - "required": ["end", "match", "knife", "init"], "type": "object" }, "canClinch": { "type": "boolean", + "nullable": true, "description": "defaults to true, means that possibly not all maps will be played if the winner is determined before" }, "matchEndAction": { - "$ref": "#/components/schemas/TMatchEndAction", + "allOf": [ + { + "$ref": "#/components/schemas/TMatchEndAction" + } + ], + "nullable": true, "description": "defaults to NONE" }, - "logs": { - "items": { - "$ref": "#/components/schemas/TLogUnion" - }, - "type": "array" - }, - "players": { - "items": { - "$ref": "#/components/schemas/IPlayer" - }, - "type": "array" - }, - "tmtSecret": { - "type": "string", - "description": "Access token to be used in the API." - }, - "isStopped": { - "type": "boolean", - "description": "If match is finished or if the match was stopped/deleted this is true." - }, - "serverPassword": { - "type": "string", - "description": "Server password, periodically fetched from game server" - }, "tmtLogAddress": { "type": "string", + "nullable": true, "description": "if set will be used to register the target logaddress for the game server" }, - "createdAt": { - "type": "number", - "format": "double", - "description": "Creation date (unix time in milliseconds since midnight, January 1, 1970 UTC)" - }, - "lastSavedAt": { - "type": "number", - "format": "double", - "description": "Last time the match was saved to disk (unix time in milliseconds since midnight, January 1, 1970 UTC)" - }, "mode": { - "$ref": "#/components/schemas/TMatchMode", + "allOf": [ + { + "$ref": "#/components/schemas/TMatchMode" + } + ], + "nullable": true, "description": "Match mode (single: stops when match is finished, loop: starts again after match is finished)" - }, - "isLive": { - "type": "boolean", - "description": "Match is currently supervised." } }, - "required": [ - "id", - "state", - "mapPool", - "teamA", - "teamB", - "electionSteps", - "election", - "gameServer", - "logSecret", - "parseIncomingLogs", - "matchMaps", - "currentMap", - "webhookUrl", - "webhookHeaders", - "rconCommands", - "canClinch", - "matchEndAction", - "logs", - "players", - "tmtSecret", - "isStopped", - "serverPassword", - "createdAt", - "mode", - "isLive" - ], + "required": ["mapPool", "teamA", "teamB", "electionSteps", "gameServer"], "type": "object", "additionalProperties": false }, @@ -1142,7 +948,12 @@ "type": "boolean" }, "teamString": { - "$ref": "#/components/schemas/TTeamString" + "allOf": [ + { + "$ref": "#/components/schemas/TTeamString" + } + ], + "nullable": true } }, "required": [ @@ -1153,7 +964,8 @@ "player", "playerTeam", "message", - "isTeamChat" + "isTeamChat", + "teamString" ], "type": "object", "additionalProperties": false @@ -1540,10 +1352,23 @@ "type": "string" }, "pickerTeam": { - "$ref": "#/components/schemas/ITeam" + "allOf": [ + { + "$ref": "#/components/schemas/ITeam" + } + ], + "nullable": true } }, - "required": ["timestamp", "matchId", "matchPassthrough", "type", "mode", "mapName"], + "required": [ + "timestamp", + "matchId", + "matchPassthrough", + "type", + "mode", + "mapName", + "pickerTeam" + ], "type": "object", "additionalProperties": false }, @@ -1574,19 +1399,49 @@ "$ref": "#/components/schemas/TSideMode" }, "pickerTeam": { - "$ref": "#/components/schemas/ITeam" + "allOf": [ + { + "$ref": "#/components/schemas/ITeam" + } + ], + "nullable": true }, "pickerSide": { - "$ref": "#/components/schemas/TTeamSides" + "allOf": [ + { + "$ref": "#/components/schemas/TTeamSides" + } + ], + "nullable": true }, "ctTeam": { - "$ref": "#/components/schemas/ITeam" + "allOf": [ + { + "$ref": "#/components/schemas/ITeam" + } + ], + "nullable": true }, "tTeam": { - "$ref": "#/components/schemas/ITeam" + "allOf": [ + { + "$ref": "#/components/schemas/ITeam" + } + ], + "nullable": true } }, - "required": ["timestamp", "matchId", "matchPassthrough", "type", "mode"], + "required": [ + "timestamp", + "matchId", + "matchPassthrough", + "type", + "mode", + "pickerTeam", + "pickerSide", + "ctTeam", + "tTeam" + ], "type": "object", "additionalProperties": false }, @@ -1609,7 +1464,7 @@ "nullable": false }, "match": { - "$ref": "#/components/schemas/IMatchResponse" + "$ref": "#/components/schemas/IMatch" } }, "required": ["timestamp", "matchId", "matchPassthrough", "type", "match"], @@ -1753,7 +1608,20 @@ "type": "array" }, "gameServer": { - "$ref": "#/components/schemas/IGameServer" + "properties": { + "rconPassword": { + "type": "string" + }, + "port": { + "type": "number", + "format": "double" + }, + "ip": { + "type": "string" + } + }, + "required": ["rconPassword", "port", "ip"], + "type": "object" }, "webhookUrl": { "type": "string", @@ -1814,33 +1682,51 @@ "description": "Match mode (single: stops when match is finished, loop: starts again after match is finished)" }, "state": { - "$ref": "#/components/schemas/TMatchState", + "allOf": [ + { + "$ref": "#/components/schemas/TMatchState" + } + ], + "nullable": true, "description": "Overwrite the match state.\nOnly sets the state. Does not execute any code/logic." }, "logSecret": { "type": "string", + "nullable": true, "description": "updates the server's log address automatically" }, "currentMap": { "type": "number", "format": "double", + "nullable": true, "description": "Change to this match map (0-based index)." }, "_restartElection": { "type": "boolean", + "nullable": true, "description": "Restart the complete match.\nWill restart the election process as well.\nMust be executed when the election steps were changed after the match was created." }, "_execRconCommandsInit": { - "type": "boolean" + "type": "boolean", + "nullable": true }, "_execRconCommandsKnife": { - "type": "boolean" + "type": "boolean", + "nullable": true }, "_execRconCommandsMatch": { - "type": "boolean" + "type": "boolean", + "nullable": true }, "_execRconCommandsEnd": { - "type": "boolean" + "type": "boolean", + "nullable": true + }, + "needsAttentionSince": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Set to `null` to reset attention timestamp." } }, "type": "object", @@ -1922,10 +1808,12 @@ }, "_refreshOvertimeAndMaxRoundsSettings": { "type": "boolean", + "nullable": true, "description": "reads and refreshes mp_overtime_enable, mp_overtime_maxrounds and mp_maxrounds from rcon" }, "_switchTeamInternals": { "type": "boolean", + "nullable": true, "description": "switch team internals, i.e. swap team names (and internal score)" } }, @@ -1944,10 +1832,6 @@ "rconPassword": { "type": "string" }, - "hideRconPassword": { - "type": "boolean", - "description": "If plebs (client without an admin token) create a match the hideRconPassword attribute is set to true.\nThis will prevent executing rcon commands from the frontend by the (unauthorized) user." - }, "canBeUsed": { "type": "boolean", "description": "Can the server be used for new matches?" @@ -1974,12 +1858,9 @@ "rconPassword": { "type": "string" }, - "hideRconPassword": { - "type": "boolean", - "description": "If plebs (client without an admin token) create a match the hideRconPassword attribute is set to true.\nThis will prevent executing rcon commands from the frontend by the (unauthorized) user." - }, "canBeUsed": { "type": "boolean", + "nullable": true, "description": "Can the server be used for new matches?" } }, @@ -2083,7 +1964,8 @@ "type": "string" }, "isPublic": { - "type": "boolean" + "type": "boolean", + "nullable": true }, "data": { "$ref": "#/components/schemas/IMatchCreateDto" @@ -2102,7 +1984,8 @@ "type": "string" }, "isPublic": { - "type": "boolean" + "type": "boolean", + "nullable": true }, "data": { "$ref": "#/components/schemas/IMatchCreateDto" @@ -2194,7 +2077,7 @@ "application/json": { "schema": { "items": { - "$ref": "#/components/schemas/IMatchResponse" + "$ref": "#/components/schemas/IMatch" }, "type": "array" } @@ -2250,6 +2133,14 @@ "schema": { "type": "boolean" } + }, + { + "in": "query", + "name": "needsAttention", + "required": false, + "schema": { + "type": "boolean" + } } ] } @@ -2265,7 +2156,7 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/IMatchResponse" + "$ref": "#/components/schemas/IMatch" }, {} ] @@ -2350,42 +2241,6 @@ ] } }, - "/api/matches/{id}/logs": { - "get": { - "operationId": "GetLogs", - "responses": { - "200": { - "description": "Ok", - "content": { - "application/json": { - "schema": { - "items": { - "type": "string" - }, - "type": "array" - } - } - } - } - }, - "description": "Get the last 1000 log lines from a specific match.", - "security": [ - { - "bearer_token": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "string" - } - } - ] - } - }, "/api/matches/{id}/events": { "get": { "operationId": "GetEvents", diff --git a/common/types/election.ts b/common/types/election.ts index 169b7bb..0b5eaee 100644 --- a/common/types/election.ts +++ b/common/types/election.ts @@ -6,8 +6,8 @@ export type TStep = 'MAP' | 'SIDE'; export interface IElection { state: TElectionState; - teamX?: TTeamAB; - teamY?: TTeamAB; + teamX: TTeamAB | null; + teamY: TTeamAB | null; /** Will be the same as the mapPool from the match, but will shrink when maps get picked, banned or randomly chosen. */ remainingMaps: string[]; /** Index of the current electionSteps of the match. */ @@ -15,7 +15,7 @@ export interface IElection { /** Toggles between MAP and SIDE */ currentSubStep: TStep; /** Current set map of the current selection step. */ - currentStepMap?: string; + currentStepMap: string | null; /** Holds the wanted maps of each team. */ currentAgree: { teamA: string | null; diff --git a/common/types/events.ts b/common/types/events.ts index 50666b2..cdde14b 100644 --- a/common/types/events.ts +++ b/common/types/events.ts @@ -1,8 +1,7 @@ import { TMapMode, TSideMode } from './electionStep'; -import { IMatchResponse } from './match'; +import { IMatch } from './match'; import { IPlayer } from './player'; -import { TTeamSides } from './stuff'; -import { ITeam, TTeamString } from './team'; +import { ITeam, TTeamSides, TTeamString } from './team'; export type EventType = | 'CHAT' @@ -37,7 +36,7 @@ export interface ChatEvent extends BaseEvent { playerTeam: ITeam | null; message: string; isTeamChat: boolean; - teamString?: TTeamString; + teamString: TTeamString | null; } export interface ElectionEndEvent extends BaseEvent { @@ -114,21 +113,21 @@ export interface ElectionMapStep extends BaseEvent { type: 'ELECTION_MAP_STEP'; mode: TMapMode; mapName: string; - pickerTeam?: ITeam; + pickerTeam: ITeam | null; } export interface ElectionSideStep extends BaseEvent { type: 'ELECTION_SIDE_STEP'; mode: TSideMode; - pickerTeam?: ITeam; - pickerSide?: TTeamSides; - ctTeam?: ITeam; - tTeam?: ITeam; + pickerTeam: ITeam | null; + pickerSide: TTeamSides | null; + ctTeam: ITeam | null; + tTeam: ITeam | null; } export interface MatchCreateEvent extends BaseEvent { type: 'MATCH_CREATE'; - match: IMatchResponse; + match: IMatch; } export interface MatchUpdateEvent extends BaseEvent { diff --git a/common/types/gameServer.ts b/common/types/gameServer.ts index fc12e82..c1eea67 100644 --- a/common/types/gameServer.ts +++ b/common/types/gameServer.ts @@ -8,19 +8,25 @@ export interface IGameServer { * If plebs (client without an admin token) create a match the hideRconPassword attribute is set to true. * This will prevent executing rcon commands from the frontend by the (unauthorized) user. */ - hideRconPassword?: boolean; + hideRconPassword: boolean; } -export interface IManagedGameServer extends IGameServer { +export interface IManagedGameServer { + ip: string; + port: number; + rconPassword: string; /** Can the server be used for new matches? */ canBeUsed: boolean; /** Match id which is currently using this managed game server. */ usedBy: IMatch['id'] | null; } -export interface IManagedGameServerCreateDto extends IGameServer { +export interface IManagedGameServerCreateDto { + ip: string; + port: number; + rconPassword: string; /** Can the server be used for new matches? */ - canBeUsed?: boolean; + canBeUsed?: boolean | null; } export interface IManagedGameServerUpdateDto { diff --git a/common/types/index.ts b/common/types/index.ts index ee427de..5dc5eb4 100644 --- a/common/types/index.ts +++ b/common/types/index.ts @@ -4,11 +4,9 @@ export * from './election'; export * from './electionStep'; export * from './events'; export * from './gameServer'; -export * from './log'; export * from './match'; export * from './matchMap'; export * from './player'; export * from './preset'; -export * from './stuff'; export * from './team'; export * from './webSocket'; diff --git a/common/types/log.ts b/common/types/log.ts deleted file mode 100644 index c24913f..0000000 --- a/common/types/log.ts +++ /dev/null @@ -1,23 +0,0 @@ -export type TLogType = 'CHAT' | 'SYSTEM'; - -export interface ILog { - type: TLogType; - timestamp: number; -} - -export interface ILogChat extends ILog { - type: 'CHAT'; - isTeamChat: boolean; - steamId64: string; - message: string; -} - -export type TSystemLogCategory = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'; - -export interface ILogSystem extends ILog { - type: 'SYSTEM'; - category: TSystemLogCategory; - message: string; -} - -export type TLogUnion = ILogChat | ILogSystem; diff --git a/common/types/match.ts b/common/types/match.ts index af44a17..a6428d1 100644 --- a/common/types/match.ts +++ b/common/types/match.ts @@ -1,7 +1,6 @@ import { IElection } from './election'; import { IElectionStep, IElectionStepAdd, IElectionStepSkip } from './electionStep'; import { IGameServer } from './gameServer'; -import { TLogUnion } from './log'; import { IMatchMap } from './matchMap'; import { IPlayer } from './player'; import { ITeam, ITeamCreateDto } from './team'; @@ -25,7 +24,7 @@ export interface IMatch { id: string; state: TMatchState; /** e.g. remote identifier, will be present in every response/webhook */ - passthrough?: string; + passthrough: string | null; /** * The maps the players can pick or ban. * Will also be used if a map is chosen randomly. @@ -81,7 +80,7 @@ export interface IMatch { /** Send various events to this url (HTTP POST) */ webhookUrl: string | null; /** Additional headers that will be added to each webhook request */ - webhookHeaders: { [key: string]: string } | null; + webhookHeaders: { [key: string]: string }; rconCommands: { /** executed exactly once on match init */ init: string[]; @@ -96,7 +95,6 @@ export interface IMatch { canClinch: boolean; /** defaults to NONE */ matchEndAction: TMatchEndAction; - logs: TLogUnion[]; players: IPlayer[]; /** Access token to be used in the API. */ tmtSecret: string; @@ -105,23 +103,22 @@ export interface IMatch { /** Server password, periodically fetched from game server */ serverPassword: string; /** if set will be used to register the target logaddress for the game server */ - tmtLogAddress?: string; + tmtLogAddress: string | null; /** Creation date (unix time in milliseconds since midnight, January 1, 1970 UTC) */ createdAt: number; /** Last time the match was saved to disk (unix time in milliseconds since midnight, January 1, 1970 UTC) */ - lastSavedAt?: number; + lastSavedAt: number | null; /** Match mode (single: stops when match is finished, loop: starts again after match is finished) */ mode: TMatchMode; -} - -export interface IMatchResponse extends IMatch { + /** Since when (unix time in milliseconds) does the match needs attention (set via ingame `.admin` command). */ + needsAttentionSince: number | null; /** Match is currently supervised. */ isLive: boolean; } export interface IMatchCreateDto { /** e.g. remote identifier, will be present in every response/webhook */ - passthrough?: string; + passthrough?: string | null; /** * The maps the players can pick or ban. * Will also be used if a map is chosen randomly. @@ -147,29 +144,33 @@ export interface IMatchCreateDto { teamA: ITeamCreateDto; teamB: ITeamCreateDto; electionSteps: Array; - gameServer: IGameServer | null; + gameServer: { + ip: string; + port: number; + rconPassword: string; + } | null; /** Send various events to this url (HTTP POST) */ webhookUrl?: string | null; /** Additional headers that will be added to each webhook request */ webhookHeaders?: { [key: string]: string } | null; rconCommands?: { /** executed exactly once on match init */ - init?: string[]; + init?: string[] | null; /** executed before every knife round */ - knife?: string[]; + knife?: string[] | null; /** executed before every match map start */ - match?: string[]; + match?: string[] | null; /** executed after last match map */ - end?: string[]; + end?: string[] | null; }; /** defaults to true, means that possibly not all maps will be played if the winner is determined before */ - canClinch?: boolean; + canClinch?: boolean | null; /** defaults to NONE */ - matchEndAction?: TMatchEndAction; + matchEndAction?: TMatchEndAction | null; /** if set will be used to register the target logaddress for the game server */ - tmtLogAddress?: string; + tmtLogAddress?: string | null; /** Match mode (single: stops when match is finished, loop: starts again after match is finished) */ - mode?: TMatchMode; + mode?: TMatchMode | null; } export interface IMatchUpdateDto extends Partial { @@ -177,22 +178,27 @@ export interface IMatchUpdateDto extends Partial { * Overwrite the match state. * Only sets the state. Does not execute any code/logic. */ - state?: TMatchState; + state?: TMatchState | null; /** updates the server's log address automatically */ - logSecret?: string; + logSecret?: string | null; /** * Change to this match map (0-based index). */ - currentMap?: number; + currentMap?: number | null; /** * Restart the complete match. * Will restart the election process as well. * Must be executed when the election steps were changed after the match was created. */ - _restartElection?: boolean; - _execRconCommandsInit?: boolean; - _execRconCommandsKnife?: boolean; - _execRconCommandsMatch?: boolean; - _execRconCommandsEnd?: boolean; + _restartElection?: boolean | null; + _execRconCommandsInit?: boolean | null; + _execRconCommandsKnife?: boolean | null; + _execRconCommandsMatch?: boolean | null; + _execRconCommandsEnd?: boolean | null; + + /** + * Set to `null` to reset attention timestamp. + */ + needsAttentionSince?: number | null; } diff --git a/common/types/matchMap.ts b/common/types/matchMap.ts index 989a507..e01f317 100644 --- a/common/types/matchMap.ts +++ b/common/types/matchMap.ts @@ -24,7 +24,7 @@ export interface IMatchMap { startAsCtTeam: TTeamAB; state: TMatchMapSate; /** Winner of the knife round which is able to or already has picked a starting side. */ - knifeWinner?: TTeamAB; + knifeWinner: TTeamAB | null; readyTeams: { teamA: boolean; teamB: boolean; @@ -51,7 +51,7 @@ export interface IMatchMap { */ export interface IMatchMapUpdateDto extends Partial { /** reads and refreshes mp_overtime_enable, mp_overtime_maxrounds and mp_maxrounds from rcon */ - _refreshOvertimeAndMaxRoundsSettings?: boolean; + _refreshOvertimeAndMaxRoundsSettings?: boolean | null; /** switch team internals, i.e. swap team names (and internal score) */ - _switchTeamInternals?: boolean; + _switchTeamInternals?: boolean | null; } diff --git a/common/types/player.ts b/common/types/player.ts index f26cfa5..386dc5a 100644 --- a/common/types/player.ts +++ b/common/types/player.ts @@ -1,5 +1,4 @@ -import { TTeamSides } from './stuff'; -import { TTeamAB } from './team'; +import { TTeamAB, TTeamSides } from './team'; /** * Player. @@ -14,9 +13,9 @@ export interface IPlayer { * If the player's steam id is in the team's `playerSteamIds64` * this cannot be changed and is always set to the team. */ - team?: TTeamAB; + team: TTeamAB | null; /** Current ingame side. */ - side?: TTeamSides | null; + side: TTeamSides | null; /** Player currently on the game server (online)? */ - online?: boolean; + online: boolean | null; } diff --git a/common/types/preset.ts b/common/types/preset.ts index b4d6240..bd04cfa 100644 --- a/common/types/preset.ts +++ b/common/types/preset.ts @@ -2,7 +2,7 @@ import { IMatchCreateDto } from './match'; export interface IPresetCreateDto { name: string; - isPublic?: boolean; + isPublic?: boolean | null; data: IMatchCreateDto; } diff --git a/common/types/stuff.ts b/common/types/stuff.ts deleted file mode 100644 index 54f117c..0000000 --- a/common/types/stuff.ts +++ /dev/null @@ -1 +0,0 @@ -export type TTeamSides = 'CT' | 'T'; diff --git a/common/types/team.ts b/common/types/team.ts index 8641bf1..3c49efe 100644 --- a/common/types/team.ts +++ b/common/types/team.ts @@ -6,13 +6,13 @@ export interface ITeam { * Passthrough data to identify team in other systems. * Will be present in every response/webhook. */ - passthrough?: string; + passthrough: string | null; /** Team name. */ name: string; /** Advantage in map wins, useful for double elimination tournament finals. */ advantage: number; /** Steam ids of players in "Steam ID 64" format. Will be forced into this team.*/ - playerSteamIds64?: string[]; + playerSteamIds64: string[]; } /** @@ -24,14 +24,16 @@ export interface ITeamCreateDto { * Passthrough data to identify team in other systems. * Will be present in every response/webhook. */ - passthrough?: string; + passthrough?: string | null; /** Advantage in map wins, useful for double elimination tournament finals. */ - advantage?: number; + advantage?: number | null; /** Steam ids of players in "Steam ID 64" format. Will be forced into this team.*/ - playerSteamIds64?: string[]; + playerSteamIds64?: string[] | null; } /** Possible ingame sides of a player. */ export type TTeamString = 'Unassigned' | 'CT' | 'TERRORIST' | '' | 'Spectator'; export type TTeamAB = 'TEAM_A' | 'TEAM_B'; + +export type TTeamSides = 'CT' | 'T'; diff --git a/common/types/webSocket.ts b/common/types/webSocket.ts index 76823e6..cee1a6c 100644 --- a/common/types/webSocket.ts +++ b/common/types/webSocket.ts @@ -1,35 +1,58 @@ -export type WebSocketMessageType = - | 'SUBSCRIBE' - | 'SUBSCRIBE_SYS' - | 'UNSUBSCRIBE' - | 'UNSUBSCRIBE_SYS'; - -export interface BaseWebSocketMessage { - type: WebSocketMessageType; -} - -export interface SubscribeMessage extends BaseWebSocketMessage { - type: 'SUBSCRIBE'; - matchId: string; - token: string; -} +import { Event } from './events'; +import { IMatch } from './match'; -export interface UnsubscribeMessage extends BaseWebSocketMessage { - matchId: string; - type: 'UNSUBSCRIBE'; -} +export type Message = { + type: 'REQUEST' | 'RESPONSE' | 'EVENT'; + /** + * Set by requester, response will have the same message id. + */ + msgId?: number; + error?: string; + payload?: T; +}; -export interface SubscribeSysMessage extends BaseWebSocketMessage { - type: 'SUBSCRIBE_SYS'; +export type Payload = + | AuthRequest + | AckResponse + | GetMatchesRequest + | GetMatchesResponse + | GetEventsRequest + | GetEventsResponse + | Event; + +export type AuthRequest = { + request: 'AUTH'; token: string; -} +}; + +// export type SubRequest = { +// request: "SUB"; +// matchId: string; +// secret: string; +// }; -export interface UnsubscribeSysMessage extends BaseWebSocketMessage { - type: 'UNSUBSCRIBE_SYS'; -} +export type AckResponse = { + type: 'ACK'; +}; + +export type GetMatchesRequest = { + request: 'GET_MATCHES'; + matches?: { + id: string; + secret?: string; + }[]; +}; + +export type GetMatchesResponse = { + matches: IMatch[]; +}; + +export type GetEventsRequest = { + request: 'GET_EVENTS'; + matchId: string; + eventTypes: Event['type'][]; +}; -export type WebSocketMessage = - | SubscribeMessage - | UnsubscribeMessage - | SubscribeSysMessage - | UnsubscribeSysMessage; +export type GetEventsResponse = { + events: Event[]; +}; diff --git a/examples/README.md b/examples/README.md index 206cff5..a59a920 100644 --- a/examples/README.md +++ b/examples/README.md @@ -47,7 +47,7 @@ Template (remove json comments before usage): "mp_give_player_c4 1; mp_startmoney 800; mp_ct_default_secondary \"weapon_hkp2000\"; mp_t_default_secondary \"weapon_glock\"", "mp_overtime_enable 1", "say > MATCH CONFIG LOADED <", - "say > HF & LG - %TMT_MAP_NUMBER%. map: %TMT_MAP_NAME% <" + "say > HF & GL - %TMT_MAP_NUMBER%. map: %TMT_MAP_NAME% <" ], "end": [ // these rcon commands will be executed only once: after the end of the last map, or when the match has been stopped (by api) "say > MATCH END RCON LOADED <" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d8d501a..62888f8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,32 +1,31 @@ { "name": "tmt2-frontend", - "version": "2.0.0", - "lockfileVersion": 2, + "version": "3.0.0", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tmt2-frontend", - "version": "2.0.0", + "version": "3.0.0", "license": "MIT", "devDependencies": { "@formkit/auto-animate": "^0.9.0", - "@solid-primitives/scheduled": "^1.5.2", - "@solidjs/router": "^0.15.3", + "@solid-primitives/scheduled": "^1.5.3", + "@solidjs/router": "^0.16.1", "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.1.17", - "daisyui": "^5.4.7", - "solid-js": "^1.9.10", - "tailwindcss": "^4.1.17", - "typescript": "^5.9.3", - "vite": "^7.2.2", - "vite-plugin-solid": "^2.11.10" + "@tailwindcss/vite": "^4.2.2", + "daisyui": "^5.5.19", + "solid-js": "^1.9.12", + "tailwindcss": "^4.2.2", + "typescript": "^6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" } }, "node_modules/@ampproject/remapping": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -37,8 +36,6 @@ }, "node_modules/@babel/code-frame": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { @@ -52,19 +49,16 @@ }, "node_modules/@babel/compat-data": { "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -92,9 +86,8 @@ }, "node_modules/@babel/generator": { "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", @@ -107,9 +100,8 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-validator-option": "^7.23.5", @@ -123,18 +115,16 @@ }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.22.15", "@babel/types": "^7.23.0" @@ -145,9 +135,8 @@ }, "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -157,9 +146,8 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.15" }, @@ -169,9 +157,8 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -188,18 +175,16 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -209,9 +194,8 @@ }, "node_modules/@babel/helper-split-export-declaration": { "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -221,8 +205,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -231,8 +213,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -241,17 +221,14 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { @@ -264,8 +241,6 @@ }, "node_modules/@babel/parser": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -280,9 +255,8 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -295,8 +269,6 @@ }, "node_modules/@babel/template": { "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { @@ -310,9 +282,8 @@ }, "node_modules/@babel/traverse": { "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -331,8 +302,6 @@ }, "node_modules/@babel/types": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", "dependencies": { @@ -343,63 +312,114 @@ "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ], + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formkit/auto-animate": { + "version": "0.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", @@ -408,13 +428,13 @@ "android" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", "cpu": [ "arm64" ], @@ -425,13 +445,13 @@ "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", "cpu": [ "x64" ], @@ -442,15 +462,15 @@ "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", @@ -459,166 +479,199 @@ "freebsd" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" + "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", "cpu": [ - "arm" + "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", "cpu": [ - "ia32" + "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", "cpu": [ - "loong64" + "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", "cpu": [ - "mips64el" + "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", "cpu": [ - "ppc64" + "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", "cpu": [ - "riscv64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "openharmony" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", "cpu": [ - "s390x" + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "cpu": [ + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", "cpu": [ "x64" ], @@ -626,16 +679,73 @@ "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@solid-primitives/scheduled": { + "version": "1.5.3", + "dev": true, + "license": "MIT", + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solidjs/router": { + "version": "0.16.1", + "dev": true, + "license": "MIT", + "peerDependencies": { + "solid-js": "^1.8.6" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", "cpu": [ "arm64" ], @@ -643,50 +753,50 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" + "android" ], "engines": { - "node": ">=18" + "node": ">= 20" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" + "darwin" ], "engines": { - "node": ">=18" + "node": ">= 20" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "darwin" ], "engines": { - "node": ">=18" + "node": ">= 20" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", "cpu": [ "x64" ], @@ -694,171 +804,139 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "freebsd" ], "engines": { - "node": ">=18" + "node": ">= 20" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", "cpu": [ - "arm64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openharmony" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 20" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", "cpu": [ - "x64" + "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ - "sunos" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 20" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 20" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", "cpu": [ - "ia32" + "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 20" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 20" } }, - "node_modules/@formkit/auto-animate": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.9.0.tgz", - "integrity": "sha512-VhP4zEAacXS3dfTpJpJ88QdLqMTcabMg0jwpOSxZ/VzfQVfl3GkZSCZThhGC5uhq/TxPHPzW0dzr4H9Bb1OgKA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", - "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", - "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", "cpu": [ "arm64" ], @@ -866,27 +944,16 @@ "license": "MIT", "optional": true, "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", - "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", - "cpu": [ - "arm64" + "win32" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "engines": { + "node": ">= 20" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", - "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", "cpu": [ "x64" ], @@ -894,3097 +961,1038 @@ "license": "MIT", "optional": true, "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", - "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", - "cpu": [ - "arm64" + "win32" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": ">= 20" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", - "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", - "cpu": [ - "x64" - ], + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", - "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", - "cpu": [ - "arm" - ], + "node_modules/@tailwindcss/vite": { + "version": "4.2.2", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "tailwindcss": "4.2.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", - "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", - "cpu": [ - "arm" - ], + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "tslib": "^2.4.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", - "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/babel__core": { + "version": "7.20.5", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", - "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", - "cpu": [ - "arm64" - ], + "node_modules/@types/babel__generator": { + "version": "7.6.8", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/types": "^7.0.0" + } }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", - "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", - "cpu": [ - "loong64" - ], + "node_modules/@types/babel__template": { + "version": "7.4.4", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", - "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/babel__traverse": { + "version": "7.20.5", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/types": "^7.20.7" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", - "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", - "cpu": [ - "riscv64" - ], + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.37.13", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "validate-html-nesting": "^1.2.1" + }, + "peerDependencies": { + "@babel/core": "^7.20.12" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", - "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", - "cpu": [ - "riscv64" - ], + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.18.6", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", - "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", - "cpu": [ - "s390x" - ], + "node_modules/babel-preset-solid": { + "version": "1.8.9", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.37.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", - "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", - "cpu": [ - "x64" - ], + "node_modules/baseline-browser-mapping": { + "version": "2.8.25", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", - "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", - "cpu": [ - "x64" - ], + "node_modules/browserslist": { + "version": "4.27.0", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", - "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", - "cpu": [ - "arm64" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", - "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", - "cpu": [ - "arm64" - ], + "node_modules/caniuse-lite": { + "version": "1.0.30001753", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", - "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", - "cpu": [ - "ia32" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", - "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", - "cpu": [ - "x64" - ], + "node_modules/csstype": { + "version": "3.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/daisyui": { + "version": "5.5.19", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "funding": { + "url": "https://github.com/saadeghi/daisyui?sponsor=1" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", - "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", - "cpu": [ - "x64" - ], + "node_modules/debug": { + "version": "4.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.245", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } }, - "node_modules/@solid-primitives/scheduled": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@solid-primitives/scheduled/-/scheduled-1.5.2.tgz", - "integrity": "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", "dev": true, "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@solidjs/router": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.15.3.tgz", - "integrity": "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==", + "node_modules/globals": { + "version": "11.12.0", "dev": true, "license": "MIT", - "peerDependencies": { - "solid-js": "^1.8.6" + "engines": { + "node": ">=4" } }, - "node_modules/@tailwindcss/node": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", - "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/html-entities": { + "version": "2.3.3", + "dev": true, + "license": "MIT" + }, + "node_modules/is-what": { + "version": "4.1.15", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.17" + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", - "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", + "node_modules/jiti": { + "version": "2.6.1", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "2.5.2", "dev": true, "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, "engines": { - "node": ">= 10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-x64": "4.1.17", - "@tailwindcss/oxide-freebsd-x64": "4.1.17", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-x64-musl": "4.1.17", - "@tailwindcss/oxide-wasm32-wasi": "4.1.17", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", - "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "android" ], "engines": { - "node": ">= 10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", - "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", - "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", - "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">= 10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", - "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", - "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10" - } + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", - "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "libc": [ + "musl" + ], + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", - "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", - "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "libc": [ + "musl" + ], + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", - "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.6.0", - "@emnapi/runtime": "^1.6.0", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.7", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" + "node": ">= 12.0.0" }, - "engines": { - "node": ">=14.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", - "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", - "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "win32" ], "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/typography": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", - "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "6.0.10" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz", - "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.17", - "@tailwindcss/oxide": "4.1.17", - "tailwindcss": "4.1.17" + "node": ">= 12.0.0" }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "node_modules/lru-cache": { + "version": "5.1.1", "dev": true, + "license": "ISC", "dependencies": { - "@babel/types": "^7.20.7" + "yallist": "^3.0.2" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-plugin-jsx-dom-expressions": { - "version": "0.37.13", - "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.37.13.tgz", - "integrity": "sha512-oAEMMIgU0h1DmHn4ZDaBBFc08nsVJciLq9pF7g0ZdpeIDKfY4zXjXr8+/oBjKhXG8nyomhnTodPjeG+/ZXcWXQ==", + "node_modules/magic-string": { + "version": "0.30.21", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "7.18.6", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.20.7", - "html-entities": "2.3.3", - "validate-html-nesting": "^1.2.1" - }, - "peerDependencies": { - "@babel/core": "^7.20.12" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "node_modules/merge-anything": { + "version": "5.1.7", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "is-what": "^4.1.8" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/babel-preset-solid": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.8.9.tgz", - "integrity": "sha512-1awR1QCoryXtAdnjsrx/eVBTYz+tpHUDOdBXqG9oVV7S0ojf2MV/woR0+8BG+LMXVzIr60oKYzCZ9UZGafxmpg==", - "dev": true, - "dependencies": { - "babel-plugin-jsx-dom-expressions": "^0.37.13" + "node": ">=12.13" }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.25", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", - "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" + "funding": { + "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "node_modules/ms": { + "version": "2.1.2", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } + "license": "MIT" }, - "node_modules/caniuse-lite": { - "version": "1.0.30001753", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", - "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", + "node_modules/nanoid": { + "version": "3.3.11", "dev": true, "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "CC-BY-4.0" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true - }, - "node_modules/daisyui": { - "version": "5.4.7", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.4.7.tgz", - "integrity": "sha512-2wYO61vTPCXk7xEBgnzLZAYoE0xS5IRLu/GSq0vORpB+cTrtubdx69NnA0loc0exvCY1s2fYL4lGZtFHe2ohNQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/saadeghi/daisyui?sponsor=1" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.245", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.245.tgz", - "integrity": "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/html-entities": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", - "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", - "dev": true - }, - "node_modules/is-what": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.15.tgz", - "integrity": "sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==", - "dev": true, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/merge-anything": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", - "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==", - "dev": true, - "dependencies": { - "is-what": "^4.1.8" - }, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/rollup": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", - "fsevents": "~2.3.2" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/seroval": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", - "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/seroval-plugins": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", - "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "seroval": "^1.0" - } - }, - "node_modules/solid-js": { - "version": "1.9.10", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", - "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "csstype": "^3.1.0", - "seroval": "~1.3.0", - "seroval-plugins": "~1.3.0" - } - }, - "node_modules/solid-refresh": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.6.3.tgz", - "integrity": "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==", - "dev": true, - "dependencies": { - "@babel/generator": "^7.23.6", - "@babel/helper-module-imports": "^7.22.15", - "@babel/types": "^7.23.6" - }, - "peerDependencies": { - "solid-js": "^1.3" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", - "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/validate-html-nesting": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/validate-html-nesting/-/validate-html-nesting-1.2.2.tgz", - "integrity": "sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==", - "dev": true - }, - "node_modules/vite": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", - "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-plugin-solid": { - "version": "2.11.10", - "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.11.10.tgz", - "integrity": "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.23.3", - "@types/babel__core": "^7.20.4", - "babel-preset-solid": "^1.8.4", - "merge-anything": "^5.1.7", - "solid-refresh": "^0.6.3", - "vitefu": "^1.0.4" - }, - "peerDependencies": { - "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", - "solid-js": "^1.7.2", - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "@testing-library/jest-dom": { - "optional": true - } - } - }, - "node_modules/vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "dev": true, - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - } - }, - "@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dev": true - }, - "@babel/core": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", - "dev": true, - "peer": true, - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - } - }, - "@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "dev": true, - "requires": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "requires": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - } - }, - "@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "dev": true, - "requires": { - "@babel/types": "^7.28.5" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - } - }, - "@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "debug": "^4.3.1", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - } - }, - "@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "dev": true, - "optional": true - }, - "@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "dev": true, - "optional": true - }, - "@formkit/auto-animate": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.9.0.tgz", - "integrity": "sha512-VhP4zEAacXS3dfTpJpJ88QdLqMTcabMg0jwpOSxZ/VzfQVfl3GkZSCZThhGC5uhq/TxPHPzW0dzr4H9Bb1OgKA==", - "dev": true - }, - "@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@rollup/rollup-android-arm-eabi": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", - "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-android-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", - "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", - "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", - "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", - "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", - "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", - "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", - "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", - "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", - "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", - "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", - "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", - "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", - "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", - "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", - "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", - "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-openharmony-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", - "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", - "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", - "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", - "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-x64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", - "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", - "dev": true, - "optional": true - }, - "@solid-primitives/scheduled": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@solid-primitives/scheduled/-/scheduled-1.5.2.tgz", - "integrity": "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA==", - "dev": true, - "requires": {} - }, - "@solidjs/router": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.15.3.tgz", - "integrity": "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==", - "dev": true, - "requires": {} - }, - "@tailwindcss/node": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", - "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", - "dev": true, - "requires": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.17" - } - }, - "@tailwindcss/oxide": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", - "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", - "dev": true, - "requires": { - "@tailwindcss/oxide-android-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-x64": "4.1.17", - "@tailwindcss/oxide-freebsd-x64": "4.1.17", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-x64-musl": "4.1.17", - "@tailwindcss/oxide-wasm32-wasi": "4.1.17", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" - } - }, - "@tailwindcss/oxide-android-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", - "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", - "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-darwin-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", - "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", - "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", - "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", - "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", - "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", - "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", - "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", - "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", - "dev": true, - "optional": true, - "requires": { - "@emnapi/core": "^1.6.0", - "@emnapi/runtime": "^1.6.0", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.7", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - } - }, - "@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", - "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", - "dev": true, - "optional": true - }, - "@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", - "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", - "dev": true, - "optional": true - }, - "@tailwindcss/typography": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", - "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", - "dev": true, - "requires": { - "postcss-selector-parser": "6.0.10" - } - }, - "@tailwindcss/vite": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz", - "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==", - "dev": true, - "requires": { - "@tailwindcss/node": "4.1.17", - "@tailwindcss/oxide": "4.1.17", - "tailwindcss": "4.1.17" - } - }, - "@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", - "dev": true, - "requires": { - "@babel/types": "^7.20.7" - } - }, - "@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true - }, - "babel-plugin-jsx-dom-expressions": { - "version": "0.37.13", - "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.37.13.tgz", - "integrity": "sha512-oAEMMIgU0h1DmHn4ZDaBBFc08nsVJciLq9pF7g0ZdpeIDKfY4zXjXr8+/oBjKhXG8nyomhnTodPjeG+/ZXcWXQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "7.18.6", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.20.7", - "html-entities": "2.3.3", - "validate-html-nesting": "^1.2.1" - }, - "dependencies": { - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - } - } - }, - "babel-preset-solid": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.8.9.tgz", - "integrity": "sha512-1awR1QCoryXtAdnjsrx/eVBTYz+tpHUDOdBXqG9oVV7S0ojf2MV/woR0+8BG+LMXVzIr60oKYzCZ9UZGafxmpg==", - "dev": true, - "requires": { - "babel-plugin-jsx-dom-expressions": "^0.37.13" - } - }, - "baseline-browser-mapping": { - "version": "2.8.25", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", - "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", - "dev": true - }, - "browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", - "dev": true, - "peer": true, - "requires": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" - } - }, - "caniuse-lite": { - "version": "1.0.30001753", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", - "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true - }, - "daisyui": { - "version": "5.4.7", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.4.7.tgz", - "integrity": "sha512-2wYO61vTPCXk7xEBgnzLZAYoE0xS5IRLu/GSq0vORpB+cTrtubdx69NnA0loc0exvCY1s2fYL4lGZtFHe2ohNQ==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.5.245", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.245.tgz", - "integrity": "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true - }, - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "html-entities": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", - "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", - "dev": true - }, - "is-what": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.15.tgz", - "integrity": "sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==", - "dev": true - }, - "jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "dev": true, - "requires": { - "detect-libc": "^2.0.3", - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "dev": true, - "optional": true - }, - "lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "dev": true, - "optional": true - }, - "lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "dev": true, - "optional": true - }, - "lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "dev": true, - "optional": true - }, - "lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "dev": true, - "optional": true - }, - "lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "dev": true, - "optional": true - }, - "lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "dev": true, - "optional": true - }, - "lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "dev": true, - "optional": true - }, - "lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "dev": true, - "optional": true - }, - "lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "dev": true, - "optional": true - }, - "lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "dev": true, - "optional": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "merge-anything": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", - "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==", - "dev": true, - "requires": { - "is-what": "^4.1.8" + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true - }, - "node-releases": { + "node_modules/node-releases": { "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true + "dev": true, + "license": "MIT" }, - "picocolors": { + "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "dev": true, + "license": "ISC" }, - "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/picomatch": { + "version": "4.0.4", "dev": true, - "peer": true + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "node_modules/postcss": { + "version": "8.5.10", "dev": true, - "requires": { + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "postcss-selector-parser": { + "node_modules/postcss-selector-parser": { "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, - "rollup": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.52.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", - "@types/estree": "1.0.8", - "fsevents": "~2.3.2" - } - }, - "semver": { + "node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "seroval": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", - "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "node_modules/seroval": { + "version": "1.5.2", "dev": true, - "peer": true + "license": "MIT", + "engines": { + "node": ">=10" + } }, - "seroval-plugins": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", - "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", + "node_modules/seroval-plugins": { + "version": "1.5.2", "dev": true, - "requires": {} + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } }, - "solid-js": { - "version": "1.9.10", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", - "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", + "node_modules/solid-js": { + "version": "1.9.12", "dev": true, - "peer": true, - "requires": { + "license": "MIT", + "dependencies": { "csstype": "^3.1.0", - "seroval": "~1.3.0", - "seroval-plugins": "~1.3.0" + "seroval": "~1.5.0", + "seroval-plugins": "~1.5.0" } }, - "solid-refresh": { + "node_modules/solid-refresh": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.6.3.tgz", - "integrity": "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" + }, + "peerDependencies": { + "solid-js": "^1.3" } }, - "source-map-js": { + "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } }, - "tailwindcss": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", - "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", + "node_modules/tailwindcss": { + "version": "4.2.2", "dev": true, - "peer": true + "license": "MIT" }, - "tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true + "node_modules/tapable": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } }, - "tinyglobby": { + "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true + "node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } }, - "update-browserslist-db": { + "node_modules/update-browserslist-db": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "dev": true, - "requires": { + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "util-deprecate": { + "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "dev": true, + "license": "MIT" }, - "validate-html-nesting": { + "node_modules/validate-html-nesting": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/validate-html-nesting/-/validate-html-nesting-1.2.2.tgz", - "integrity": "sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==", - "dev": true - }, - "vite": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", - "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", - "dev": true, - "peer": true, - "requires": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "fsevents": "~2.3.3", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", + "dev": true, + "license": "ISC" + }, + "node_modules/vite": { + "version": "8.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "vite-plugin-solid": { - "version": "2.11.10", - "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.11.10.tgz", - "integrity": "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw==", + "node_modules/vite-plugin-solid": { + "version": "2.11.12", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" + }, + "peerDependencies": { + "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", + "solid-js": "^1.7.2", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@testing-library/jest-dom": { + "optional": true + } } }, - "vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "node_modules/vitefu": { + "version": "1.1.3", "dev": true, - "requires": {} + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } }, - "yallist": { + "node_modules/yallist": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" } } } diff --git a/frontend/package.json b/frontend/package.json index f53251b..185c534 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "tmt2-frontend", - "version": "2.0.0", + "version": "3.0.0", "repository": "https://github.com/JensForstmann/tmt2", "author": "Jens Forstmann", "license": "MIT", @@ -11,15 +11,15 @@ }, "devDependencies": { "@formkit/auto-animate": "^0.9.0", - "@solid-primitives/scheduled": "^1.5.2", - "@solidjs/router": "^0.15.3", + "@solid-primitives/scheduled": "^1.5.3", + "@solidjs/router": "^0.16.1", "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.1.17", - "daisyui": "^5.4.7", - "solid-js": "^1.9.10", - "tailwindcss": "^4.1.17", - "typescript": "^5.9.3", - "vite": "^7.2.2", - "vite-plugin-solid": "^2.11.10" + "@tailwindcss/vite": "^4.2.2", + "daisyui": "^5.5.19", + "solid-js": "^1.9.12", + "tailwindcss": "^4.2.2", + "typescript": "^6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 17bd250..3f713c3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,83 +1,168 @@ -import { A, AnchorProps, RouteSectionProps } from '@solidjs/router'; -import { Component, Match, Switch, onMount } from 'solid-js'; -import { SvgComputer, SvgDarkMode, SvgLightMode } from './assets/Icons'; -import logo from './assets/logo.svg'; +import { RouteSectionProps } from '@solidjs/router'; +import { Component, createEffect, createSignal, onMount } from 'solid-js'; +import { createStore } from 'solid-js/store'; +import { + AuthRequest, + ChatEvent, + Event, + GetEventsRequest, + GetEventsResponse, + GetMatchesRequest, + GetMatchesResponse, + IMatch, + LogEvent, +} from '../../common'; +import { ErrorComponent } from './components/ErrorComponent'; +import { NavBar } from './components/NavBar'; +import { getToken } from './utils/auth'; +import { updateDarkClasses } from './utils/theme'; +import { createWebSocket } from './utils/webSocket'; -import { isLoggedIn } from './utils/fetcher'; -import { t } from './utils/locale'; -import { currentTheme, cycleDarkMode, updateDarkClasses } from './utils/theme'; +type GlobalStoreMatch = { + data: IMatch; + /** undefined until explicitly requested */ + chatEvents?: ChatEvent[]; + /** undefined until explicitly requested */ + logEvents?: LogEvent[]; +}; +const [globalStore, setGlobalStore] = createStore<{ + matches?: GlobalStoreMatch[]; +}>(); +export { globalStore }; -const NavLink = (props: AnchorProps) => { - return ( - - {props.children} - - ); +export const fetchMatch = async (matchId: string, secret?: string) => { + const matches = await fetchMatches([{ id: matchId, secret: secret }]); + if (matches[0]) { + return matches[0]; + } }; -const NavBar: Component = () => { - return ( - - ); +export const fetchMatches = async (matches: GetMatchesRequest['matches']) => { + const res = await sendRequest({ + request: 'GET_MATCHES', + matches: matches, + }); + setGlobalStore('matches', (existing): GlobalStoreMatch[] => { + const existingWithoutNew = + existing?.filter((e) => res.matches.find((m) => m.id === e.data.id) === undefined) ?? + []; + return [ + ...existingWithoutNew, + ...res.matches.map( + (r): GlobalStoreMatch => ({ + data: r, + }) + ), + ]; + }); + return res.matches; +}; + +export const fetchMatchEvents = async (id: string) => { + const existing = globalStore.matches?.findIndex((m) => m.data.id === id) ?? -1; + if (existing < 0) { + throw 'Match is unknown, cannot get events'; + } + const res = await sendRequest({ + request: 'GET_EVENTS', + matchId: id, + eventTypes: ['CHAT', 'LOG'], + }); + const chatEvents = res.events.filter((e) => e.type === 'CHAT'); + const logEvents = res.events.filter((e) => e.type === 'LOG'); + setGlobalStore('matches', existing, 'chatEvents', chatEvents); + setGlobalStore('matches', existing, 'logEvents', logEvents); +}; + +const [connectionState, setConnectionState] = createSignal<'CLOSED' | 'AUTHED' | 'ANONYMOUS'>( + 'CLOSED' +); +export { connectionState }; + +const onEvent = (event: Event) => { + if (event.type === 'MATCH_CREATE') { + setGlobalStore('matches', (existing) => [...(existing ?? []), { data: event.match }]); + return; + } + + if (!globalStore.matches) { + return; + } + const matchIndex = globalStore.matches.findIndex((m) => m.data.id === event.matchId); + if (matchIndex < 0) { + return; + } + const match = globalStore.matches[matchIndex]; + if (event.type === 'CHAT' && match.chatEvents) { + setGlobalStore('matches', matchIndex, 'chatEvents', (existing) => [ + ...(existing ?? []), + event, + ]); + } else if (event.type === 'LOG' && match.logEvents) { + setGlobalStore('matches', matchIndex, 'logEvents', (existing) => [ + ...(existing ?? []), + event, + ]); + } else if (event.type === 'MATCH_UPDATE') { + (setGlobalStore as any)('matches', matchIndex, 'data', ...event.path, event.value); + } }; +const [errorMessage, setErrorMessage] = createSignal(''); + +const onConnect = () => { + if (!globalStore || !globalStore.matches || globalStore.matches.length === 0) { + return; + } + + // refetch all matches from global store to receive events again + fetchMatches( + globalStore.matches.map((m) => ({ id: m.data.id, secret: m.data.tmtSecret })) + ).catch((err) => setErrorMessage(err + '')); +}; + +const WebSocket = createWebSocket({ + onEvent: onEvent, + afterConnect: onConnect, + connect: true, + autoReconnect: true, +}); +export const sendRequest = WebSocket.sendRequest; + export const App: Component = (props) => { onMount(updateDarkClasses); + + createEffect(() => { + const state = WebSocket.state(); + const token = getToken(); + if (state === 'OPEN' && !token) { + setConnectionState('ANONYMOUS'); + return; + } + if (state === 'OPEN' && token) { + sendRequest({ + request: 'AUTH', + token: token, + }) + .then(() => setConnectionState('AUTHED')) + .catch((err) => setErrorMessage(err + '')); + return; + } + if (state !== 'OPEN') { + setConnectionState('CLOSED'); + return; + } + }); + return ( <>
-
{props.children}
+
+ + {props.children} +
); diff --git a/frontend/src/assets/Icons.tsx b/frontend/src/assets/Icons.tsx index 8680736..672110a 100644 --- a/frontend/src/assets/Icons.tsx +++ b/frontend/src/assets/Icons.tsx @@ -1,6 +1,6 @@ import type { Component, ComponentProps } from 'solid-js'; -// icons from: https://fonts.google.com/icons?icon.set=Material+Icons --> download as svg +// icons from: https://fonts.google.com/icons?icon.set=Material+Icons --> copy button (or download as svg) export const SvgLightMode: Component> = (props) => ( > = (props) => ( ); + +// https://fonts.google.com/icons?icon.set=Material+Icons&selected=Material+Icons+Outlined:login:&icon.query=login&icon.size=24&icon.color=%23e3e3e3 +export const SvgLogin: Component> = (props) => ( + + + + + + + + +); + +// https://fonts.google.com/icons?icon.set=Material+Icons&selected=Material+Icons+Outlined:logout:&icon.query=logout&icon.size=24&icon.color=%23e3e3e3 +export const SvgLogout: Component> = (props) => ( + + + + + + + + +); + +// https://fonts.google.com/icons?icon.set=Material+Icons&selected=Material+Icons+Outlined:color_lens:&icon.query=color&icon.size=24&icon.color=%23e3e3e3 +export const SvgTheme: Component> = (props) => ( + + + + + + + + +); + +// https://fonts.google.com/icons?icon.set=Material+Icons&selected=Material+Icons+Outlined:info:&icon.query=info&icon.size=24&icon.color=%23e3e3e3 +export const SvgInfo: Component> = (props) => ( + + + + +); + +// https://fonts.google.com/icons?icon.set=Material+Icons&selected=Material+Icons+Outlined:notifications:&icon.query=notifications&icon.size=24&icon.color=%23e3e3e3 +export const SvgNotifications: Component> = (props) => ( + + + + +); diff --git a/frontend/src/components/Chat.tsx b/frontend/src/components/Chat.tsx index c5bd254..39433ab 100644 --- a/frontend/src/components/Chat.tsx +++ b/frontend/src/components/Chat.tsx @@ -1,5 +1,6 @@ import { Component, createSignal } from 'solid-js'; -import { ChatEvent } from '../../../common'; +import { ChatEvent, escapeRconSayString } from '../../../common'; +import { createFetcher } from '../utils/fetcher'; import { t } from '../utils/locale'; import { onEnter } from '../utils/onEnter'; import { Card } from './Card'; @@ -9,9 +10,15 @@ import { ScrollArea } from './ScrollArea'; export const Chat: Component<{ messages: ChatEvent[]; - sendMessage: (msg: string) => Promise; + matchId: string; + secret?: string; }> = (props) => { const [errorMessage, setErrorMessage] = createSignal(''); + const fetcher = createFetcher(props.secret); + const sendChatMessage = (msg: string) => + fetcher('POST', `/api/matches/${props.matchId}/server/rcon`, [ + `say ${escapeRconSayString(msg)}`, + ]); return (

{t('Chat')}

@@ -23,8 +30,7 @@ export const Chat: Component<{ const input = e.currentTarget; const msg = input.value.trim(); if (msg) { - props - .sendMessage(msg) + sendChatMessage(msg) .then(() => { input.value = ''; setErrorMessage(''); diff --git a/frontend/src/components/CreateUpdateMatch.tsx b/frontend/src/components/CreateUpdateMatch.tsx index 860638a..33f2ce7 100644 --- a/frontend/src/components/CreateUpdateMatch.tsx +++ b/frontend/src/components/CreateUpdateMatch.tsx @@ -4,8 +4,8 @@ import { createStore } from 'solid-js/store'; import { IConfig, IElectionStep, + IMatch, IMatchCreateDto, - IMatchResponse, IMatchUpdateDto, IPreset, IPresetCreateDto, @@ -24,12 +24,13 @@ import { SvgVisiblityOff, } from '../assets/Icons'; import { copyObject } from '../utils/copyObject'; -import { createFetcher, isLoggedIn } from '../utils/fetcher'; +import { createFetcher } from '../utils/fetcher'; import { t } from '../utils/locale'; import { AddElectionStep, getElectionStepString } from './ElectionStep'; import { ErrorComponent } from './ErrorComponent'; import { SelectInput, TextArea, TextInput, CheckboxInput } from './Inputs'; import { Modal } from './Modal'; +import { isLoggedIn } from '../utils/auth'; const Presets: Component<{ onSelect: (preset: IMatchCreateDto) => void; @@ -247,7 +248,7 @@ export const CreateUpdateMatch: Component< } | { mode: 'UPDATE'; - match: IMatchResponse & { isStopped?: boolean }; + match: IMatch; } ) & { callback: (data: IMatchUpdateDto & IMatchCreateDto) => Promise; @@ -924,7 +925,7 @@ export const CreateUpdateMatch: Component< labelTopRight={t( 'Ends match series after a map if a winner is determined' )} - checked={dto.canClinch} + checked={dto.canClinch ?? undefined} class={getChangedClasses( props.match.canClinch, dto.canClinch, diff --git a/frontend/src/components/EditableTextField.tsx b/frontend/src/components/EditableTextField.tsx deleted file mode 100644 index 47b0836..0000000 --- a/frontend/src/components/EditableTextField.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Component, createSignal, Match, splitProps, Switch } from 'solid-js'; - -export const EditableTextField: Component<{ - label: string; - initialValue: string; - onSave: (newValue: string) => Promise; - editable: boolean; - align: 'left' | 'right'; -}> = (props) => { - const [local, other] = splitProps(props, ['label', 'initialValue', 'onSave']); - const [value, setValue] = createSignal(local.initialValue); - const [state, setState] = createSignal<'same' | 'changed' | 'saving' | 'error' | 'saved'>( - 'same' - ); - const [timeId, setTimeId] = createSignal(); - const save = async () => { - if (state() === 'same' || state() === 'saving') { - return; - } - setState('saving'); - const resp = await local.onSave(value()); - if (resp) { - setState('saved'); - clearTimeout(timeId()); - setTimeId( - setTimeout( - () => setState(value() === local.initialValue ? 'same' : 'changed'), - 1000 - ) - ); - } else { - setState('error'); - clearTimeout(timeId()); - setTimeId( - setTimeout( - () => setState(value() === local.initialValue ? 'same' : 'changed'), - 1000 - ) - ); - } - }; - - // const adornment = save()} - // style={state() === 'same' ? 'visibility: hidden;' : ''} - // > - // }> - // - // - // - // - // - // ; - - return ( - - ); -}; diff --git a/frontend/src/components/GameServerCard.tsx b/frontend/src/components/GameServerCard.tsx index f774b1a..a83b2ca 100644 --- a/frontend/src/components/GameServerCard.tsx +++ b/frontend/src/components/GameServerCard.tsx @@ -1,5 +1,5 @@ import { Component } from 'solid-js'; -import { IMatchResponse } from '../../../common'; +import { IMatch } from '../../../common'; import { SvgCopyAll, SvgOpenInNew } from '../assets/Icons'; import { t } from '../utils/locale'; import { Card } from './Card'; @@ -32,7 +32,7 @@ export const GameServerCard: Component<{ }; export const MatchGameServerCard: Component<{ - match: IMatchResponse; + match: IMatch; }> = (props) => { return ( = (props) => { const navigate = useNavigate(); const [searchParams] = useSearchParams(); diff --git a/frontend/src/components/MatchList.tsx b/frontend/src/components/MatchList.tsx index 7b2903e..1bc9500 100644 --- a/frontend/src/components/MatchList.tsx +++ b/frontend/src/components/MatchList.tsx @@ -1,6 +1,6 @@ import { A } from '@solidjs/router'; import { Component, For, Show } from 'solid-js'; -import { IMatchResponse, getTotalNumberOfMaps } from '../../../common'; +import { getTotalNumberOfMaps, IMatch } from '../../../common'; import { SvgNavigateNext } from '../assets/Icons'; import { t } from '../utils/locale'; @@ -9,6 +9,7 @@ export const MatchTableColumns = [ 'TEAM_B', 'ONLINE_PLAYER_COUNT', 'AGE', + 'NEEDS_ATTENTION', 'BEST_OF', 'MATCH_STATE', 'CURRENT_MAP', @@ -23,6 +24,7 @@ export const MatchTableColumnLabels: Record = { TEAM_B: t('Team B'), ONLINE_PLAYER_COUNT: t('Online Players'), AGE: t('Age'), + NEEDS_ATTENTION: t('Needs Attention'), BEST_OF: t('Best of'), MATCH_STATE: t('Match State'), CURRENT_MAP: t('Current Map'), @@ -36,14 +38,14 @@ export type TMatchTableColumns = (typeof MatchTableColumns)[number]; export type TColumnsToShow = Partial>; -const diffString = (createdAt: number) => { +const diffString = (createdAt: number | null) => { if (createdAt) { return Math.round((Date.now() - createdAt) / 1000 / 60) + 'min'; } return ''; }; -export const MatchList: Component<{ matches: IMatchResponse[]; columnsToShow: TColumnsToShow }> = ( +export const MatchList: Component<{ matches: IMatch[]; columnsToShow: TColumnsToShow }> = ( props ) => { const cts = () => props.columnsToShow; @@ -58,6 +60,7 @@ export const MatchList: Component<{ matches: IMatchResponse[]; columnsToShow: TC {MatchTableColumnLabels.ONLINE_PLAYER_COUNT} )} {cts().AGE && {MatchTableColumnLabels.AGE}} + {cts().NEEDS_ATTENTION && {MatchTableColumnLabels.NEEDS_ATTENTION}} {cts().BEST_OF && {MatchTableColumnLabels.BEST_OF}} {cts().MATCH_STATE && {MatchTableColumnLabels.MATCH_STATE}} {cts().CURRENT_MAP && {MatchTableColumnLabels.CURRENT_MAP}} @@ -82,6 +85,9 @@ export const MatchList: Component<{ matches: IMatchResponse[]; columnsToShow: TC )} {cts().AGE && {diffString(match.createdAt)}} + {cts().NEEDS_ATTENTION && ( + {diffString(match.needsAttentionSince)} + )} {cts().BEST_OF && {getTotalNumberOfMaps(match.electionSteps)}} {cts().MATCH_STATE && {match.state}} {cts().CURRENT_MAP && ( diff --git a/frontend/src/components/MatchMapCard.tsx b/frontend/src/components/MatchMapCard.tsx index f07e7be..328780f 100644 --- a/frontend/src/components/MatchMapCard.tsx +++ b/frontend/src/components/MatchMapCard.tsx @@ -1,8 +1,8 @@ import { Component, createSignal } from 'solid-js'; import { + IMatch, IMatchMap, IMatchMapUpdateDto, - IMatchResponse, IMatchUpdateDto, TMatchMapSate, TTeamAB, @@ -18,7 +18,7 @@ import { Modal } from './Modal'; import { RoundBackups } from './RoundBackups'; export const MatchMapCard: Component<{ - match: IMatchResponse; + match: IMatch; map: IMatchMap; mapIndex: number; }> = (props) => { diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx new file mode 100644 index 0000000..001338a --- /dev/null +++ b/frontend/src/components/NavBar.tsx @@ -0,0 +1,140 @@ +import { A, AnchorProps } from '@solidjs/router'; +import { Component, Match, Show, Switch } from 'solid-js'; +import { globalStore } from '../App'; +import { + SvgCheck, + SvgInfo, + SvgLogin, + SvgLogout, + SvgNotifications, + SvgSettings, + SvgTheme, +} from '../assets/Icons'; +import logo from '../assets/logo.svg'; +import { MatchesNeedingAttention } from '../pages/matches'; +import { isLoggedIn } from '../utils/auth'; +import { t } from '../utils/locale'; +import { currentTheme, setTheme } from '../utils/theme'; + +const NavLink = (props: AnchorProps) => { + return ( + + {props.children} + + ); +}; + +export const NavBar: Component = () => { + const matchesNeedingAttention = () => + globalStore.matches?.filter((m) => m.data.isLive && m.data.needsAttentionSince); + return ( + + ); +}; diff --git a/frontend/src/components/NeedsAttentionCard.tsx b/frontend/src/components/NeedsAttentionCard.tsx new file mode 100644 index 0000000..f514ad4 --- /dev/null +++ b/frontend/src/components/NeedsAttentionCard.tsx @@ -0,0 +1,30 @@ +import { Component, createSignal } from 'solid-js'; +import { IMatch, IMatchUpdateDto } from '../../../common'; +import { createFetcher } from '../utils/fetcher'; +import { t } from '../utils/locale'; +import { Card } from './Card'; +import { ErrorComponent } from './ErrorComponent'; + +export const NeedsAttentionCard: Component<{ + match: IMatch; +}> = (props) => { + const fetcher = createFetcher(props.match.tmtSecret); + + const [errorMessage, setErrorMessage] = createSignal(''); + const unsetNeedsAttention = () => + fetcher('PATCH', `/api/matches/${props.match.id}`, { + needsAttentionSince: null, + } satisfies IMatchUpdateDto).catch((err) => setErrorMessage(err + '')); + + return ( + +

{t('Match needs attention')}

+

{t('An admin was called by a player.')}

+ +
+ +
+ ); +}; diff --git a/frontend/src/components/NotLiveCard.tsx b/frontend/src/components/NotLiveCard.tsx index 6888b6d..9f648d7 100644 --- a/frontend/src/components/NotLiveCard.tsx +++ b/frontend/src/components/NotLiveCard.tsx @@ -1,12 +1,12 @@ import { Component, createSignal, Show } from 'solid-js'; -import { IMatchResponse } from '../../../common'; import { createFetcher } from '../utils/fetcher'; import { t } from '../utils/locale'; import { Card } from './Card'; import { ErrorComponent } from './ErrorComponent'; +import { IMatch } from '../../../common'; export const NotLiveCard: Component<{ - match: IMatchResponse; + match: IMatch; }> = (props) => { const fetcher = createFetcher(props.match.tmtSecret); const reload = () => location.reload(); diff --git a/frontend/src/components/PlayerListCard.tsx b/frontend/src/components/PlayerListCard.tsx index fc1b7b4..7e0a9f4 100644 --- a/frontend/src/components/PlayerListCard.tsx +++ b/frontend/src/components/PlayerListCard.tsx @@ -1,10 +1,10 @@ import { Component, For, Show } from 'solid-js'; -import { IMatchResponse, IPlayer } from '../../../common'; +import { IMatch, IPlayer } from '../../../common'; import { t } from '../utils/locale'; import { Card } from './Card'; export const PlayerListCard: Component<{ - match: IMatchResponse; + match: IMatch; }> = (props) => { return ( diff --git a/frontend/src/components/Rcon.tsx b/frontend/src/components/Rcon.tsx index 76f3d83..2f50ae4 100644 --- a/frontend/src/components/Rcon.tsx +++ b/frontend/src/components/Rcon.tsx @@ -1,5 +1,5 @@ import { Component, createSignal } from 'solid-js'; -import { IMatchResponse } from '../../../common'; +import { IMatch } from '../../../common'; import { createFetcher } from '../utils/fetcher'; import { t } from '../utils/locale'; import { onEnter } from '../utils/onEnter'; @@ -85,7 +85,7 @@ const RconCard: Component<{ }; export const Rcon: Component<{ - match: IMatchResponse; + match: IMatch; }> = (props) => { const fetcher = createFetcher(props.match.tmtSecret); return ( @@ -99,7 +99,7 @@ export const Rcon: Component<{ export const RconServer: Component<{ ip: string; - port: number; + port: number | string; }> = (props) => { const fetcher = createFetcher(); return ( diff --git a/frontend/src/components/RoundBackups.tsx b/frontend/src/components/RoundBackups.tsx index 90b2712..8f86550 100644 --- a/frontend/src/components/RoundBackups.tsx +++ b/frontend/src/components/RoundBackups.tsx @@ -1,5 +1,5 @@ import { Component, createSignal, For, Show } from 'solid-js'; -import { IMatchResponse } from '../../../common'; +import { IMatch } from '../../../common'; import { createFetcher } from '../utils/fetcher'; import { t } from '../utils/locale'; import { mustConfirm } from '../utils/mustConfirm'; @@ -7,7 +7,7 @@ import { Loader } from './Loader'; import { SelectInput } from './Inputs'; export const RoundBackups: Component<{ - match: IMatchResponse; + match: IMatch; roundBackupFiles?: string[]; onClose?: () => void; }> = (props) => { diff --git a/frontend/src/pages/create.tsx b/frontend/src/pages/create.tsx index 6b4bc85..a361119 100644 --- a/frontend/src/pages/create.tsx +++ b/frontend/src/pages/create.tsx @@ -1,5 +1,5 @@ import { useNavigate } from '@solidjs/router'; -import { Component, createSignal } from 'solid-js'; +import { Component } from 'solid-js'; import { IMatch } from '../../../common'; import { Card } from '../components/Card'; import { CreateUpdateMatch, getSimpleElectionSteps } from '../components/CreateUpdateMatch'; @@ -28,7 +28,7 @@ const DEFAULT_RCON_MATCH = [ 'mp_give_player_c4 1; mp_startmoney 800; mp_ct_default_secondary "weapon_hkp2000"; mp_t_default_secondary "weapon_glock"', 'mp_overtime_enable 1', 'say > MATCH CONFIG LOADED <', - 'say > HF & LG - %TMT_MAP_NUMBER%. map: %TMT_MAP_NAME% <', + 'say > HF & GL - %TMT_MAP_NUMBER%. map: %TMT_MAP_NAME% <', ]; const DEFAULT_RCON_END = ['say > MATCH END RCON LOADED <']; diff --git a/frontend/src/pages/gameServer.tsx b/frontend/src/pages/gameServer.tsx index 84a3682..36541a9 100644 --- a/frontend/src/pages/gameServer.tsx +++ b/frontend/src/pages/gameServer.tsx @@ -8,9 +8,9 @@ import { t } from '../utils/locale'; export const GameServerPage: Component = () => { const params = useParams(); - const parts = params.ipPort.split(':', 2); - const ip = parts[0]; - const port = Number.parseInt(parts[1]); + const parts = params.ipPort?.split(':', 2); + const ip = parts?.[0]; + const port = parts?.[1]; const [serverPassword, setServerPassword] = createSignal(''); const fetcher = createFetcher(); @@ -26,7 +26,7 @@ export const GameServerPage: Component = () => { ); }); - return Number.isNaN(port) ? ( + return !ip || !port ? ( ) : (
diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index d20ae0f..fe4d2e5 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -1,9 +1,9 @@ import { useNavigate, useSearchParams } from '@solidjs/router'; import { Component, createSignal } from 'solid-js'; -import { getToken, login } from '../utils/fetcher'; -import { t } from '../utils/locale'; import { Card } from '../components/Card'; import { TextInput } from '../components/Inputs'; +import { getToken, login } from '../utils/auth'; +import { t } from '../utils/locale'; export const LoginPage: Component = () => { const [searchParams] = useSearchParams(); diff --git a/frontend/src/pages/logout.tsx b/frontend/src/pages/logout.tsx index 370e1ee..5ec21d3 100644 --- a/frontend/src/pages/logout.tsx +++ b/frontend/src/pages/logout.tsx @@ -1,5 +1,5 @@ import { Component } from 'solid-js'; -import { logout } from '../utils/fetcher'; +import { logout } from '../utils/auth'; import { t } from '../utils/locale'; export const LogoutPage: Component = () => { diff --git a/frontend/src/pages/match.tsx b/frontend/src/pages/match.tsx index f426aa4..8ef1c33 100644 --- a/frontend/src/pages/match.tsx +++ b/frontend/src/pages/match.tsx @@ -1,104 +1,65 @@ import { useParams, useSearchParams } from '@solidjs/router'; -import { Component, createEffect, For, onCleanup, onMount, Show } from 'solid-js'; -import { createStore } from 'solid-js/store'; -import { ChatEvent, escapeRconSayString, Event, IMatchResponse, LogEvent } from '../../../common'; +import { Component, createEffect, For, Show } from 'solid-js'; +import { connectionState, fetchMatch, fetchMatchEvents, globalStore } from '../App'; import { Chat } from '../components/Chat'; import { MatchGameServerCard } from '../components/GameServerCard'; import { Loader } from '../components/Loader'; import { LogViewer } from '../components/LogViewer'; import { MatchCard } from '../components/MatchCard'; import { MatchMapCard } from '../components/MatchMapCard'; +import { NeedsAttentionCard } from '../components/NeedsAttentionCard'; import { NotLiveCard } from '../components/NotLiveCard'; import { PlayerListCard } from '../components/PlayerListCard'; import { Rcon } from '../components/Rcon'; -import { createFetcher } from '../utils/fetcher'; -import { createWebSocket } from '../utils/webSocket'; export const MatchPage: Component = () => { const params = useParams(); const [searchParams] = useSearchParams(); - const fetcher = createFetcher( - typeof searchParams.secret === 'string' ? searchParams.secret : undefined - ); - const [data, setData] = createStore<{ - match?: IMatchResponse; - logEvents?: LogEvent[]; - chatEvents?: ChatEvent[]; - }>({}); - - onMount(async () => { - fetcher('GET', `/api/matches/${params.id}`).then((match) => { - setData('match', match); - }); - fetcher('GET', `/api/matches/${params.id}/events`).then((events) => { - if (!events) { - return; - } - setData( - 'chatEvents', - events.filter((event): event is ChatEvent => event.type === 'CHAT') - ); - setData( - 'logEvents', - events.filter((event): event is LogEvent => event.type === 'LOG') - ); - }); - }); + const secret = () => + typeof searchParams.secret === 'string' ? searchParams.secret : undefined; + const match = () => globalStore.matches?.find((m) => m.data.id === params.id)?.data; + const logEvents = () => globalStore.matches?.find((m) => m.data.id === params.id)?.logEvents; + const chatEvents = () => globalStore.matches?.find((m) => m.data.id === params.id)?.chatEvents; - const onWsMsg = (msg: Event) => { - if (msg.type === 'CHAT') { - setData('chatEvents', (existing) => [...(existing ?? []), msg]); - } else if (msg.type === 'LOG') { - setData('logEvents', (existing) => [...(existing ?? []), msg]); - } else if (msg.type === 'MATCH_UPDATE') { - (setData as any)('match', ...msg.path, msg.value); + createEffect(async () => { + const state = connectionState(); + const m = match(); + const l = logEvents(); + const s = secret(); + const canFetch = state === 'AUTHED' || (state === 'ANONYMOUS' && s); + if ((m && l) || !canFetch) { + return; } - }; - - const { state, subscribe, disconnect, connect } = createWebSocket(onWsMsg, { - autoReconnect: true, - }); - - createEffect(() => { - if (data.match) { - connect(); + if (!m && params.id) { + fetchMatch(params.id, s); + return; } - }); - - createEffect(() => { - if (state() === 'OPEN' && data.match) { - subscribe({ - matchId: params.id, - token: data.match.tmtSecret, - }); + if (!l && params.id) { + fetchMatchEvents(params.id); } }); - onCleanup(() => disconnect()); - - const sendChatMessage = (msg: string) => - fetcher('POST', `/api/matches/${params.id}/server/rcon`, [ - `say ${escapeRconSayString(msg)}`, - ]); - return ( - }> + }> {(match) => (
+ + + {(map, i) => } - - + + - - + +
diff --git a/frontend/src/pages/matchEdit.tsx b/frontend/src/pages/matchEdit.tsx index d55c48b..2a71a04 100644 --- a/frontend/src/pages/matchEdit.tsx +++ b/frontend/src/pages/matchEdit.tsx @@ -1,7 +1,7 @@ import { A, useNavigate, useParams, useSearchParams } from '@solidjs/router'; import { Component, Show, onMount } from 'solid-js'; import { createStore } from 'solid-js/store'; -import { IMatchCreateDto, IMatchResponse, IMatchUpdateDto } from '../../../common'; +import { IMatchCreateDto, IMatch, IMatchUpdateDto } from '../../../common'; import { SvgNavigateBefore } from '../assets/Icons'; import { Card } from '../components/Card'; import { CreateUpdateMatch } from '../components/CreateUpdateMatch'; @@ -17,11 +17,8 @@ const getUpdateDtoAttribute = (pre: T, post: T): T | undefined => { return pre !== post ? post : undefined; }; -const getUpdateDto = ( - match: IMatchResponse, - dto: IMatchCreateDto & IMatchUpdateDto -): IMatchUpdateDto => { - const newDto = { +const getUpdateDto = (match: IMatch, dto: IMatchCreateDto & IMatchUpdateDto): IMatchUpdateDto => { + const newDto: IMatchUpdateDto = { passthrough: getUpdateDtoAttribute(match.passthrough, dto.passthrough), mapPool: getUpdateDtoAttribute(match.mapPool, dto.mapPool), teamA: getUpdateDtoAttribute(match.teamA, dto.teamA), @@ -62,11 +59,11 @@ export const MatchEditPage: Component = () => { typeof searchParams.secret === 'string' ? searchParams.secret : undefined ); const [data, setData] = createStore<{ - match?: IMatchResponse; + match?: IMatch; }>({}); onMount(async () => { - fetcher('GET', `/api/matches/${params.id}`).then((match) => { + fetcher('GET', `/api/matches/${params.id}`).then((match) => { setData('match', match); }); }); @@ -92,11 +89,7 @@ export const MatchEditPage: Component = () => { mode="UPDATE" match={match()} callback={async (dto) => { - await fetcher( - 'PATCH', - `/api/matches/${params.id}`, - getUpdateDto(match(), dto) - ); + await fetcher('PATCH', `/api/matches/${params.id}`, dto); navigate(matchLink()); }} getFinalDto={(dto) => diff --git a/frontend/src/pages/matches.tsx b/frontend/src/pages/matches.tsx index 759d270..f323e49 100644 --- a/frontend/src/pages/matches.tsx +++ b/frontend/src/pages/matches.tsx @@ -1,9 +1,9 @@ import { useSearchParams } from '@solidjs/router'; -import { Component, createEffect, createSignal, For, onCleanup, Show } from 'solid-js'; -import { createStore } from 'solid-js/store'; -import { Event, IMatchResponse } from '../../../common'; +import { Component, createEffect, createSignal, For, Show } from 'solid-js'; +import { connectionState, fetchMatches, globalStore } from '../App'; import { SvgSettings } from '../assets/Icons'; import { Card } from '../components/Card'; +import { ErrorComponent } from '../components/ErrorComponent'; import { SelectInput } from '../components/Inputs'; import { MatchList, @@ -11,9 +11,7 @@ import { MatchTableColumns, TColumnsToShow, } from '../components/MatchList'; -import { createFetcher, getToken } from '../utils/fetcher'; import { t } from '../utils/locale'; -import { createWebSocket } from '../utils/webSocket'; const defaultColumns: TColumnsToShow = { TEAM_A: true, @@ -31,57 +29,46 @@ const defaultColumns: TColumnsToShow = { type FilterOption = { title: string; - search: string; + filter: string; label: string; - /** indicates, if new matches should be appended to the table */ - includeNewMatches: boolean; +}; +export const MatchesNeedingAttention: FilterOption = { + title: t('Matches needing Attention'), + filter: 'isLive=true&needsAttention=true', + label: t('Show matches which needs attention'), }; const filterOptions: FilterOption[] = [ { title: t('Live Matches'), - search: '?isLive=true', + filter: 'isLive=true', label: t('Only show matches that are currently being supervised'), - includeNewMatches: true, }, { title: t('Not Live'), - search: '?isLive=false', + filter: 'isLive=false', label: t('Only show offline matches (not supervised)'), - includeNewMatches: false, }, { title: t('Dangling Matches'), - search: '?isLive=false&isStopped=false&state=ELECTION&state=MATCH_MAP', + filter: 'isLive=false&isStopped=false&states=ELECTION,MATCH_MAP', label: t('Only show offline matches which have not been properly stopped'), - includeNewMatches: false, }, + MatchesNeedingAttention, ]; +const DEFAULT_FILTER = filterOptions[0]; export const MatchesPage: Component = () => { const [searchParams, setSearchParams] = useSearchParams<{ - searchString?: string; + filter?: string; columns?: string; - includeNewMatches?: string; }>(); - const fetcher = createFetcher(); - const [data, setData] = createStore<{ - matches?: IMatchResponse[]; - }>({}); const [filterLabel, setFilterLabel] = createSignal(''); + const [errorMessage, setErrorMessage] = createSignal(''); const getCurrentFilterOption = () => - !searchParams.searchString - ? filterOptions[0] - : filterOptions.find((fo) => fo.search === searchParams.searchString); - - createEffect(() => { - fetcher( - 'GET', - `/api/matches${searchParams.searchString || filterOptions[0].search}` - ).then((matches) => { - setData('matches', matches); - }); - }); + !searchParams.filter + ? DEFAULT_FILTER + : filterOptions.find((fo) => fo.filter === searchParams.filter); createEffect(() => { const filterOption = getCurrentFilterOption(); @@ -92,6 +79,28 @@ export const MatchesPage: Component = () => { } }); + const matchesToShow = () => { + const filter = new URLSearchParams(searchParams.filter ?? DEFAULT_FILTER.filter); + const wantedStates = filter.get('states')?.split(',') ?? []; + const convertToBoolean = (value: string | null) => { + return value === 'true' ? true : value === 'false' ? false : undefined; + }; + const isLive = convertToBoolean(filter.get('isLive')); + const isStopped = convertToBoolean(filter.get('isStopped')); + const needsAttention = convertToBoolean(filter.get('needsAttention')); + return globalStore.matches?.filter((m) => { + return ( + (wantedStates.length === 0 || wantedStates.includes(m.data.state)) && + (isLive === undefined || m.data.isLive === isLive) && + (isStopped === undefined || m.data.isStopped === isStopped) && + (needsAttention === undefined || + (needsAttention === true + ? m.data.needsAttentionSince !== null + : m.data.needsAttentionSince === null)) + ); + }); + }; + const columnsToShow = (): TColumnsToShow => { const fromStorage = localStorage.getItem('columns'); if (!searchParams.columns && !fromStorage) { @@ -107,7 +116,7 @@ export const MatchesPage: Component = () => { }; const updateColumnsSearchParam = (columnsToShow: TColumnsToShow) => { - const columns: string[] = [' ']; + const columns: string[] = []; MatchTableColumns.forEach((mtc) => { if (columnsToShow[mtc]) { columns.push(mtc); @@ -118,68 +127,20 @@ export const MatchesPage: Component = () => { setSearchParams({ columns: str }, { replace: true }); }; - const onWsMsg = (msg: Event) => { - if (msg.type === 'MATCH_UPDATE') { - const mapIndex = data.matches?.findIndex((match) => match.id === msg.matchId); - if (mapIndex !== undefined && mapIndex >= 0) { - (setData as any)('matches', mapIndex, ...msg.path, msg.value); - } - } else if (msg.type === 'MATCH_CREATE') { - if ( - searchParams.includeNewMatches === 'true' || - getCurrentFilterOption()?.includeNewMatches - ) { - setData('matches', (existing) => [...(existing ?? []), msg.match]); - } - } - }; - - const { state, subscribe, subscribeSys, unsubscribe, disconnect } = createWebSocket(onWsMsg, { - autoReconnect: true, - connect: true, - }); - createEffect(() => { - const token = getToken(); - if (state() === 'OPEN' && token) { - subscribeSys(token); + const matches = globalStore.matches; + const state = connectionState(); + if (!matches && state === 'AUTHED') { + fetchMatches(undefined).catch((err) => setErrorMessage(err + '')); } }); - createEffect((previousSubs?: string[]): string[] => { - const subs = previousSubs ?? []; - if (state() === 'OPEN') { - // unsubscribe - subs.forEach((matchId) => { - if (!data.matches?.find((match) => match.id === matchId)) { - console.info(`Unsub from ${matchId}`); - unsubscribe(matchId); - } - }); - // subscribe - data.matches?.forEach((match) => { - if (!subs.includes(match.id)) { - console.info(`Sub to ${match.id}`); - subscribe({ - matchId: match.id, - token: match.tmtSecret, - }); - } - }); - // update subscription list - return data.matches?.map((match) => match.id) ?? []; - } - return []; - }); - - onCleanup(() => disconnect()); - return (
- setSearchParams({ searchString: e.currentTarget.value }, { replace: true }) + setSearchParams({ filter: e.currentTarget.value }, { replace: true }) } labelBottomLeft={filterLabel()} > @@ -189,10 +150,10 @@ export const MatchesPage: Component = () => { - {(filterOption) => ( + {(filterOption, i) => ( @@ -235,8 +196,14 @@ export const MatchesPage: Component = () => {
- - {(matches) => } + + + {(matches) => ( + m.data)} + columnsToShow={columnsToShow()} + /> + )}
); diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts new file mode 100644 index 0000000..459fccc --- /dev/null +++ b/frontend/src/utils/auth.ts @@ -0,0 +1,37 @@ +import { useNavigate } from '@solidjs/router'; +import { createSignal } from 'solid-js'; +import { API_HOST } from './fetcher'; + +export const [isLoggedIn, setIsLoggedIn] = createSignal(); + +export const getToken = () => (isLoggedIn() ? localStorage.getItem('token') : null); + +const isTokenOk = async (token: string | null): Promise => { + if (token === null) { + return false; + } + const res = await fetch(`${API_HOST}/api/login`, { + method: 'POST', + headers: { + Authorization: token, + }, + }); + return res.status >= 200 && res.status <= 299; +}; + +setIsLoggedIn(await isTokenOk(localStorage.getItem('token'))); + +export const login = async (token: string): Promise => { + if (await isTokenOk(token)) { + localStorage.setItem('token', token); + setIsLoggedIn(true); + return true; + } + return false; +}; + +export const logout = () => { + localStorage.removeItem('token'); + setIsLoggedIn(false); + useNavigate()('/'); +}; diff --git a/frontend/src/utils/fetcher.ts b/frontend/src/utils/fetcher.ts index bb9195a..bf97903 100644 --- a/frontend/src/utils/fetcher.ts +++ b/frontend/src/utils/fetcher.ts @@ -1,68 +1,24 @@ -import { useNavigate } from '@solidjs/router'; -import { createSignal } from 'solid-js'; +import { getToken, setIsLoggedIn } from './auth'; import { t } from './locale'; -const API_HOST = import.meta.env.DEV +export const API_HOST = import.meta.env.DEV ? `${window.location.protocol}//${window.location.hostname}:8080` : ''; -export const getToken = () => localStorage.getItem('token'); - -const isTokenOk = async (token?: string): Promise => { - const tkn = token ?? getToken(); - const response = await fetch(`${API_HOST}/api/login`, { - method: 'POST', - headers: { - ...(tkn ? { Authorization: tkn } : {}), - }, - }); - if (response.status >= 200 && response.status <= 299) { - return true; - } - return false; -}; - -const [isLoggedIn, setIsLoggedIn] = createSignal(); -export { isLoggedIn }; -isTokenOk().then((ok) => setIsLoggedIn(ok)); - -export const login = async (token: string): Promise => { - if (await isTokenOk(token)) { - localStorage.setItem('token', token); - setIsLoggedIn(true); - return true; - } - return false; -}; - -export const logout = () => { - localStorage.removeItem('token'); - setIsLoggedIn(false); - useNavigate()('/'); -}; - type HttpMethods = 'GET' | 'PATCH' | 'POST' | 'DELETE' | 'PUT'; export const createFetcher = (token?: string) => { - return async ( - method: HttpMethods, - path: string, - body?: any, - init?: RequestInit - ): Promise => { + return async (method: HttpMethods, path: string, body?: any): Promise => { const tkn = getToken() ?? token; const response = await fetch(`${API_HOST}${path}`, { - ...init, method: method, headers: { 'Content-Type': 'application/json; charset=UTF-8', ...(tkn ? { Authorization: tkn } : {}), - ...init?.headers, }, body: body ? JSON.stringify(body) : undefined, }); - const status = response.status; - if (status === 401) { + if (response.status === 401) { setIsLoggedIn(false); const { pathname, search, hash } = window.location; const encodedPath = encodeURIComponent(pathname + search + hash); diff --git a/frontend/src/utils/theme.ts b/frontend/src/utils/theme.ts index 9ea1529..f89f520 100644 --- a/frontend/src/utils/theme.ts +++ b/frontend/src/utils/theme.ts @@ -8,8 +8,10 @@ export const updateDarkClasses = () => { } }; -const [currentTheme, setCurrentTheme] = createSignal<'light' | 'dark' | 'system'>( - (localStorage.getItem('theme') as 'light' | 'dark' | null) ?? 'system' +type Theme = 'light' | 'dark' | 'system'; + +const [currentTheme, setCurrentTheme] = createSignal( + (localStorage.getItem('theme') as Theme | null) ?? 'system' ); export { currentTheme }; @@ -17,9 +19,9 @@ const systemWantsDark = () => window.matchMedia('(prefers-color-scheme: dark)'). // if system wants dark: system -> light -> dark -> // if system wants light: system -> dark -> light -> -export const cycleDarkMode = () => { +export const cycleTheme = () => { const swd = systemWantsDark(); - let next: 'light' | 'dark' | 'system'; + let next: Theme; const ct = currentTheme(); if (swd && ct === 'system') { @@ -37,8 +39,12 @@ export const cycleDarkMode = () => { } else { next = 'system'; } - setCurrentTheme(next); - localStorage.setItem('theme', next); + setTheme(next); +}; + +export const setTheme = (theme: Theme) => { + setCurrentTheme(theme); + localStorage.setItem('theme', theme); updateDarkClasses(); }; diff --git a/frontend/src/utils/webSocket.ts b/frontend/src/utils/webSocket.ts index 204b3ac..f7daba7 100644 --- a/frontend/src/utils/webSocket.ts +++ b/frontend/src/utils/webSocket.ts @@ -1,27 +1,41 @@ -import { createEffect, createSignal } from 'solid-js'; -import { - Event, - SubscribeMessage, - SubscribeSysMessage, - UnsubscribeMessage, - UnsubscribeSysMessage, -} from '../../../common'; +import { createSignal } from 'solid-js'; +import { Event, Message, Payload } from '../../../common'; const WS_HOST = import.meta.env.DEV ? `${window.location.protocol.replace('http', 'ws')}//${window.location.hostname}:8080` : `${window.location.protocol.replace('http', 'ws')}//${window.location.host}`; type Options = { + onEvent?: (event: Event) => void; + afterConnect?: () => void; connect?: boolean; autoReconnect?: boolean; }; -export const createWebSocket = (onMsg: (msg: Event) => void, options?: Options) => { + +type Requests = Map< + number, + { + resolve: (result: any) => void; + reject: (result: any) => void; + } +>; + +export const createWebSocket = (options?: Options) => { + const requests: Requests = new Map(); let ws: WebSocket | undefined; + let msgId = 0; const [state, setState] = createSignal<'CLOSED' | 'CLOSING' | 'CONNECTING' | 'OPEN' | 'NEW'>( 'NEW' ); + const onClose = () => { + setState('CLOSED'); + if (options?.autoReconnect) { + setTimeout(() => reconnect(), 1_000); + } + }; + const reconnect = () => { if (ws) { ws.onclose = null; @@ -32,54 +46,30 @@ export const createWebSocket = (onMsg: (msg: Event) => void, options?: Options) } setState('CONNECTING'); const newWs = new WebSocket(`${WS_HOST}/ws`); - newWs.onclose = () => setState('CLOSED'); - newWs.onerror = () => setState('CLOSED'); - newWs.onopen = () => setState('OPEN'); + newWs.onclose = () => onClose(); + newWs.onerror = () => onClose(); + newWs.onopen = () => { + setState('OPEN'); + options?.afterConnect?.(); + }; newWs.onmessage = (ev) => { - let msg: Event | undefined; + let msg: Message | undefined; try { msg = JSON.parse(ev.data); - } catch (err) { + } catch (err) {} + if (!msg) { console.warn('Could not parse webSocket message'); - } - if (msg) { - onMsg(msg); + } else if (msg.type === 'RESPONSE') { + onResponse(msg); + } else if (msg.type === 'EVENT') { + options?.onEvent?.(msg.payload as Event); + } else { + console.warn(`WebSocket type ${msg.type} is not implemented`); } }; ws = newWs; }; - const subscribe = (msg: Omit) => { - const m: SubscribeMessage = { - ...msg, - type: 'SUBSCRIBE', - }; - ws?.send(JSON.stringify(m)); - }; - - const subscribeSys = (token: string) => { - const m: SubscribeSysMessage = { - type: 'SUBSCRIBE_SYS', - token: token, - }; - ws?.send(JSON.stringify(m)); - }; - - const unsubscribe = (matchId: string) => { - const m: UnsubscribeMessage = { - matchId: matchId, - type: 'UNSUBSCRIBE', - }; - ws?.send(JSON.stringify(m)); - }; - - const unsubscribeSys = () => { - const m: UnsubscribeSysMessage = { - type: 'UNSUBSCRIBE_SYS', - }; - ws?.send(JSON.stringify(m)); - }; - const disconnect = () => { setState('CLOSING'); ws?.close(); @@ -89,20 +79,44 @@ export const createWebSocket = (onMsg: (msg: Event) => void, options?: Options) reconnect(); } - createEffect(() => { - if (state() === 'CLOSED' && options?.autoReconnect) { - reconnect(); + const sendRequest = ( + payload: RequestPayload + ): Promise => { + return new Promise((resolve, reject) => { + if (!ws || state() !== 'OPEN') { + reject('WebSocket is not ready'); + } + const message = { + type: 'REQUEST', + msgId: msgId++, + payload: payload, + } satisfies Message; + requests.set(message.msgId, { + resolve: resolve, + reject: reject, + }); + ws?.send(JSON.stringify(message)); + }); + }; + + const onResponse = (response: Message) => { + const request = requests.get(response.msgId!); + if (!request) { + console.error(`Request with message id ${response.msgId} could not be found`); + return; + } + if (response.error) { + request.reject(response.error); + } else { + request.resolve(response.payload); } - }); + }; return { state, - subscribe, - subscribeSys, - unsubscribe, - unsubscribeSys, disconnect, reconnect, connect: reconnect, + sendRequest, }; }; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 7a1bd98..826e7e6 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -3,7 +3,7 @@ "strict": true, "target": "ESNext", "module": "ESNext", - "moduleResolution": "node", + "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "jsx": "preserve", diff --git a/package-lock.json b/package-lock.json index f53b36e..658d36d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,24 @@ { "name": "tmt2", - "version": "2.0.0", - "lockfileVersion": 2, + "version": "3.0.0", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tmt2", - "version": "2.0.0", + "version": "3.0.0", "license": "MIT", "devDependencies": { - "prettier": "^3.6.2", - "prettier-plugin-tailwindcss": "^0.7.1" + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.7.2" } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -31,9 +30,9 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.1.tgz", - "integrity": "sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz", + "integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==", "dev": true, "license": "MIT", "engines": { @@ -109,21 +108,5 @@ } } } - }, - "dependencies": { - "prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", - "dev": true, - "peer": true - }, - "prettier-plugin-tailwindcss": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.1.tgz", - "integrity": "sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ==", - "dev": true, - "requires": {} - } } } diff --git a/package.json b/package.json index 7a18052..e9639a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tmt2", - "version": "2.0.0", + "version": "3.0.0", "description": "TMT is a tool that tracks/watches/observes a Counter-Strike 2 match.", "repository": "github:JensForstmann/tmt2", "author": "Jens Forstmann", @@ -12,7 +12,7 @@ "prettier-check": "prettier '!**/*md' . --check" }, "devDependencies": { - "prettier": "^3.6.2", - "prettier-plugin-tailwindcss": "^0.7.1" + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.7.2" } }