From 18e09342da6b56dbd439c9670eeefd267883549c Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Fri, 19 Jun 2026 04:47:18 +0800 Subject: [PATCH 01/17] feat: game options validation, Hide & Seek manager, role system, API events, matchmaker improvements, and config updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit === Game Options Validation (src/game/GameOptions.ts) [NEW] === + GameOptionsValidator.validateGameSettings() — validates all game settings + Numeric range checks: playerSpeed, killCooldown, vision, tasks, timers + Enum checks: GameMap, GameKeyword, KillDistance, TaskBarMode, GameMode, RulesPreset + Dependency check: numEmergencies <= maxPlayers + Role settings validation for all 9 roles + Used in WaterwayServer.handleHostGameMessage() on room creation + Used in Room constructor on player.syncsettings RPC === Hide & Seek Game Mode Manager (src/game/modes/HideAndSeekManager.ts) [NEW] === + Full lifecycle: Hiding -> Seeking -> FinalHide -> Ended + Automatic seeker selection (configured playerId or random) + Hider tracking with task-completion win condition + Body reports and meetings disabled during gameplay + Final hide phase: seeker speed boost, position ping mechanic + Player disconnect handling: - Seeker leaves -> hiders win - All hiders leave -> seeker wins + Integrated into Room.handleStartGame() and Room.handleRpcMessage() + Clean teardown on destroy / game end === Role System (src/game/roles/) [NEW] === [NEW] BaseRole.ts — server-side role base class + Cooldown management (startCooldown, canUseAbility, isOnCooldown) + Hooks: onGameStart, onKill, onTaskComplete, onDeath, onMeetingStart, onAbilityUse, onFixedUpdate, onGameEnd + roleType, teamType, isActive, currentCooldown, lastAbilityUse [NEW] NoisemakerRole.ts — crewmate, emits alert on task completion + onTaskComplete(): broadcasts position reveal to nearby players + Configurable alert duration and impostor visibility [NEW] PhantomRole.ts — impostor, vanishes on death + onDeath(): enters invisible ghost state instead of dying + Vanish duration + cooldown from role settings + Auto-dies after vanish expires [NEW] TrackerRole.ts — crewmate, tracks a target + onAbilityUse(target): starts periodic position updates + Tracking duration, cooldown, and update delay from role settings + Auto-stops after duration [NEW] DetectiveRole.ts — crewmate, inspects suspects + onAbilityUse(target): checks kill count, reports suspicion level + suspectLimit from role settings + Tracks murder records during game [NEW] ViperRole.ts — impostor, delayed poison kills + onKill(target): applies poison instead of instant kill + Target dies after viperDissolveTime seconds + All poisons cleared on Viper death or game end [NEW] RoleManager.ts — role assignment and lifecycle + assignRoles(): reads roleChances from settings, rolls dice per player + Respects maxPlayers limits and team constraints + Routes game events (kill, task, death, meeting) to correct role + Plugin-extensible via static registerRole() === New API Events (src/api/events/) [NEW] === [NEW] room/HideAndSeekStart.ts — room.hideandseekstart Fields: room, seekerPlayer, hiderPlayers, hideDuration [NEW] room/HiderCaught.ts — room.hidercaught Fields: room, hiderPlayer, seekerPlayer, remainingHiders [NEW] room/HideAndSeekEnd.ts — room.hideandseekend Fields: room, winner ("seekers"|"hiders"|"none"), reason [NEW] player/TaskStarted.ts — player.taskstarted Fields: room, player, taskType, taskId [NEW] player/TaskProgress.ts — player.taskprogress Fields: room, player, taskId, progress [NEW] player/TaskCompleted.ts — player.taskcompleted Fields: room, player, taskType, taskId, isLongTask [NEW] player/LevelChanged.ts — player.levelchanged Fields: room, player, oldLevel, newLevel Emitted in Room.on("player.setlevel") handler [NEW] player/MurderFail.ts — player.murderfail Fields: room, player (killer), target, reason, details? Emitted from Room.on("player.checkmurder") when murder is invalid [MODIFY] events/room/index.ts — export HideAndSeekStart, HideAndSeekEnd, HiderCaught [NEW] events/player/index.ts — export all new player events [MODIFY] events/index.ts — export player module === HTTP Matchmaker (src/Matchmaker.ts) [MODIFY] === [NEW] GET / — server dashboard HTML page Shows: room/player/connection/plugin counts, public room list table, API endpoint reference. Dark theme, zero external CSS. [ENHANCE] GET /api/games/:id — now returns full room info + Players array: clientId, username, color, hat, skin, visor, isHost, isDead + GameState, GameMode, Privacy strings [FIX] GET /api/filtertags — was stub returning [], now dynamic + Scans active rooms for maps, languages, game modes + Merges with configurable static filterTags from server config + Returns FilterTagJson[] with Name, DisplayName, Type, Count [NEW] POST /api/games/:id/refresh — room metadata refresh + Host can update privacy and room name + Returns updated GameListingJson === Config Updates (src/WaterwayServer.ts, bin/createDefaultConfig.ts) [MODIFY] === [ADD] GameListingConfig.filterTags Type: { name: string; displayName: string; icon?: string }[] Served by GET /api/filtertags as static filter buttons [ADD] RoomsConfig.allowedGameModes Type: number[] — restrict which game modes rooms can use Empty array = all modes allowed [ADD] RoomsConfig.defaultGameMode Type: number — game mode for new rooms (default 1 = Normal) [ENHANCE] Room constructor applies enforceSettings from config on creation Settings in enforceSettings cannot be overridden by host [MODIFY] bin/createDefaultConfig.ts — all new fields with defaults === Room Integration (src/Room.ts) [MODIFY] === + gameModeManager property — active game mode handler (HideAndSeekManager) + roleManager property — role assignment and lifecycle + Validates game options on player.syncsettings (rejects invalid) + Emits PlayerLevelChangedEvent on player.setlevel + Emits PlayerTaskCompletedEvent on player.completetask + Emits PlayerMurderFailEvent on player.checkmurder (when !isValid) + Routes kill/task/death through roleManager + Routes RPCs through gameModeManager + Cleanup in _reset() and destroy() === Verification === - TypeScript compilation passes cleanly (tsc --noEmit exits 0) - All events follow existing Waterway event patterns - Role classes extend Waterway BaseRole (server-side), not SkeldJS BaseRole --- bin/createDefaultConfig.ts | 5 +- changelog.txt | 149 + package-lock.json | 2701 +++++++++++++ src/Matchmaker.ts | 408 +- src/Room.ts | 152 + src/WaterwayServer.ts | 42 + src/api/events/index.ts | 1 + src/api/events/player/LevelChanged.ts | 30 + src/api/events/player/MurderFail.ts | 42 + src/api/events/player/TaskCompleted.ts | 34 + src/api/events/player/TaskProgress.ts | 30 + src/api/events/player/TaskStarted.ts | 30 + src/api/events/player/index.ts | 5 + src/api/events/room/HideAndSeekEnd.ts | 28 + src/api/events/room/HideAndSeekStart.ts | 31 + src/api/events/room/HiderCaught.ts | 30 + src/api/events/room/index.ts | 3 + src/game/GameOptions.ts | 595 +++ src/game/index.ts | 1 + src/game/modes/HideAndSeekManager.ts | 472 +++ src/game/modes/index.ts | 1 + src/game/roles/BaseRole.ts | 120 + src/game/roles/DetectiveRole.ts | 110 + src/game/roles/NoisemakerRole.ts | 84 + src/game/roles/PhantomRole.ts | 110 + src/game/roles/RoleManager.ts | 242 ++ src/game/roles/TrackerRole.ts | 132 + src/game/roles/ViperRole.ts | 134 + src/game/roles/index.ts | 7 + yarn.lock | 4603 ++++++++--------------- 30 files changed, 7342 insertions(+), 2990 deletions(-) create mode 100644 changelog.txt create mode 100644 package-lock.json create mode 100644 src/api/events/player/LevelChanged.ts create mode 100644 src/api/events/player/MurderFail.ts create mode 100644 src/api/events/player/TaskCompleted.ts create mode 100644 src/api/events/player/TaskProgress.ts create mode 100644 src/api/events/player/TaskStarted.ts create mode 100644 src/api/events/player/index.ts create mode 100644 src/api/events/room/HideAndSeekEnd.ts create mode 100644 src/api/events/room/HideAndSeekStart.ts create mode 100644 src/api/events/room/HiderCaught.ts create mode 100644 src/game/GameOptions.ts create mode 100644 src/game/index.ts create mode 100644 src/game/modes/HideAndSeekManager.ts create mode 100644 src/game/modes/index.ts create mode 100644 src/game/roles/BaseRole.ts create mode 100644 src/game/roles/DetectiveRole.ts create mode 100644 src/game/roles/NoisemakerRole.ts create mode 100644 src/game/roles/PhantomRole.ts create mode 100644 src/game/roles/RoleManager.ts create mode 100644 src/game/roles/TrackerRole.ts create mode 100644 src/game/roles/ViperRole.ts create mode 100644 src/game/roles/index.ts diff --git a/bin/createDefaultConfig.ts b/bin/createDefaultConfig.ts index 4db6a4845..374755883 100644 --- a/bin/createDefaultConfig.ts +++ b/bin/createDefaultConfig.ts @@ -26,7 +26,8 @@ export function createDefaultConfig(): WaterwayConfig { ignoreSearchTerms: false, maxResults: 10, removeExtraFilters: false, - requireExactMatches: true + requireExactMatches: true, + filterTags: [] }, plugins: { loadDirectory: true @@ -52,6 +53,8 @@ export function createDefaultConfig(): WaterwayConfig { }, gameCodes: "v2", enforceSettings: {}, + allowedGameModes: [], + defaultGameMode: 1, authoritativeServer: false, advanced: { unknownObjects: false diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 000000000..8f1433171 --- /dev/null +++ b/changelog.txt @@ -0,0 +1,149 @@ +feat: game options validation, Hide & Seek manager, role system, API events, matchmaker improvements, and config updates + +=== Game Options Validation (src/game/GameOptions.ts) [NEW] === + + GameOptionsValidator.validateGameSettings() — validates all game settings + + Numeric range checks: playerSpeed, killCooldown, vision, tasks, timers + + Enum checks: GameMap, GameKeyword, KillDistance, TaskBarMode, GameMode, + RulesPreset + + Dependency check: numEmergencies <= maxPlayers + + Role settings validation for all 9 roles + + Used in WaterwayServer.handleHostGameMessage() on room creation + + Used in Room constructor on player.syncsettings RPC + +=== Hide & Seek Game Mode Manager (src/game/modes/HideAndSeekManager.ts) [NEW] === + + Full lifecycle: Hiding -> Seeking -> FinalHide -> Ended + + Automatic seeker selection (configured playerId or random) + + Hider tracking with task-completion win condition + + Body reports and meetings disabled during gameplay + + Final hide phase: seeker speed boost, position ping mechanic + + Player disconnect handling: + - Seeker leaves -> hiders win + - All hiders leave -> seeker wins + + Integrated into Room.handleStartGame() and Room.handleRpcMessage() + + Clean teardown on destroy / game end + +=== Role System (src/game/roles/) [NEW] === + + [NEW] BaseRole.ts — server-side role base class + + Cooldown management (startCooldown, canUseAbility, isOnCooldown) + + Hooks: onGameStart, onKill, onTaskComplete, onDeath, onMeetingStart, + onAbilityUse, onFixedUpdate, onGameEnd + + roleType, teamType, isActive, currentCooldown, lastAbilityUse + + [NEW] NoisemakerRole.ts — crewmate, emits alert on task completion + + onTaskComplete(): broadcasts position reveal to nearby players + + Configurable alert duration and impostor visibility + + [NEW] PhantomRole.ts — impostor, vanishes on death + + onDeath(): enters invisible ghost state instead of dying + + Vanish duration + cooldown from role settings + + Auto-dies after vanish expires + + [NEW] TrackerRole.ts — crewmate, tracks a target + + onAbilityUse(target): starts periodic position updates + + Tracking duration, cooldown, and update delay from role settings + + Auto-stops after duration + + [NEW] DetectiveRole.ts — crewmate, inspects suspects + + onAbilityUse(target): checks kill count, reports suspicion level + + suspectLimit from role settings + + Tracks murder records during game + + [NEW] ViperRole.ts — impostor, delayed poison kills + + onKill(target): applies poison instead of instant kill + + Target dies after viperDissolveTime seconds + + All poisons cleared on Viper death or game end + + [NEW] RoleManager.ts — role assignment and lifecycle + + assignRoles(): reads roleChances from settings, rolls dice per player + + Respects maxPlayers limits and team constraints + + Routes game events (kill, task, death, meeting) to correct role + + Plugin-extensible via static registerRole() + +=== New API Events (src/api/events/) [NEW] === + + [NEW] room/HideAndSeekStart.ts — room.hideandseekstart + Fields: room, seekerPlayer, hiderPlayers, hideDuration + + [NEW] room/HiderCaught.ts — room.hidercaught + Fields: room, hiderPlayer, seekerPlayer, remainingHiders + + [NEW] room/HideAndSeekEnd.ts — room.hideandseekend + Fields: room, winner ("seekers"|"hiders"|"none"), reason + + [NEW] player/TaskStarted.ts — player.taskstarted + Fields: room, player, taskType, taskId + + [NEW] player/TaskProgress.ts — player.taskprogress + Fields: room, player, taskId, progress + + [NEW] player/TaskCompleted.ts — player.taskcompleted + Fields: room, player, taskType, taskId, isLongTask + + [NEW] player/LevelChanged.ts — player.levelchanged + Fields: room, player, oldLevel, newLevel + Emitted in Room.on("player.setlevel") handler + + [NEW] player/MurderFail.ts — player.murderfail + Fields: room, player (killer), target, reason, details? + Emitted from Room.on("player.checkmurder") when murder is invalid + + [MODIFY] events/room/index.ts — export HideAndSeekStart, HideAndSeekEnd, + HiderCaught + [NEW] events/player/index.ts — export all new player events + [MODIFY] events/index.ts — export player module + +=== HTTP Matchmaker (src/Matchmaker.ts) [MODIFY] === + + [NEW] GET / — server dashboard HTML page + Shows: room/player/connection/plugin counts, public room list table, + API endpoint reference. Dark theme, zero external CSS. + + [ENHANCE] GET /api/games/:id — now returns full room info + + Players array: clientId, username, color, hat, skin, visor, + isHost, isDead + + GameState, GameMode, Privacy strings + + [FIX] GET /api/filtertags — was stub returning [], now dynamic + + Scans active rooms for maps, languages, game modes + + Merges with configurable static filterTags from server config + + Returns FilterTagJson[] with Name, DisplayName, Type, Count + + [NEW] POST /api/games/:id/refresh — room metadata refresh + + Host can update privacy and room name + + Returns updated GameListingJson + +=== Config Updates (src/WaterwayServer.ts, bin/createDefaultConfig.ts) [MODIFY] === + + [ADD] GameListingConfig.filterTags + Type: { name: string; displayName: string; icon?: string }[] + Served by GET /api/filtertags as static filter buttons + + [ADD] RoomsConfig.allowedGameModes + Type: number[] — restrict which game modes rooms can use + Empty array = all modes allowed + + [ADD] RoomsConfig.defaultGameMode + Type: number — game mode for new rooms (default 1 = Normal) + + [ENHANCE] Room constructor applies enforceSettings from config on creation + Settings in enforceSettings cannot be overridden by host + + [MODIFY] bin/createDefaultConfig.ts — all new fields with defaults + +=== Room Integration (src/Room.ts) [MODIFY] === + + + gameModeManager property — active game mode handler (HideAndSeekManager) + + roleManager property — role assignment and lifecycle + + Validates game options on player.syncsettings (rejects invalid) + + Emits PlayerLevelChangedEvent on player.setlevel + + Emits PlayerTaskCompletedEvent on player.completetask + + Emits PlayerMurderFailEvent on player.checkmurder (when !isValid) + + Routes kill/task/death through roleManager + + Routes RPCs through gameModeManager + + Cleanup in _reset() and destroy() + +=== Verification === + - TypeScript compilation passes cleanly (tsc --noEmit exits 0) + - All events follow existing Waterway event patterns + - Role classes extend Waterway BaseRole (server-side), not SkeldJS BaseRole diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..5e99ae13d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2701 @@ +{ + "name": "@skeldjs/waterway", + "version": "3.0.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@skeldjs/waterway", + "version": "3.0.3", + "license": "GPL-3.0-only", + "dependencies": { + "@koa/router": "^14.0.0", + "@schemastore/package": "^0.0.6", + "@skeldjs/au-client": "^3.0.3", + "@skeldjs/au-constants": "^3.0.3", + "@skeldjs/au-core": "^3.0.3", + "@skeldjs/au-protocol": "^3.0.3", + "@skeldjs/au-text": "^3.0.3", + "@skeldjs/events": "^3.0.3", + "@skeldjs/hazel": "^3.0.3", + "chalk": "^4.1.1", + "chokidar": "^3.5.2", + "compare-versions": "^3.6.0", + "dotenv": "^10.0.0", + "koa": "^3.0.3", + "koa-body": "^6.0.1", + "minimatch": "^3.0.4", + "prompts": "^2.4.2", + "query-registry": "^2.5.0", + "reflect-metadata": "^0.1.13", + "resolve-from": "^5.0.0", + "resolve-pkg": "^2.0.0", + "triple-beam": "^1.3.0", + "vorpal": "^1.12.0" + }, + "bin": { + "waterway": "dist/bin/pkg.js" + }, + "devDependencies": { + "@types/koa": "^2", + "@types/koa__router": "^12", + "@types/minimatch": "^3.0.5", + "@types/node": "^24.7.0", + "@types/prompts": "^2.0.14", + "@types/triple-beam": "^1.3.2", + "@types/vorpal": "^1.12.0", + "@types/yargs": "^17.0.0", + "pinst": "^2.1.6", + "ts-node": "^10.0.0", + "typedoc": "^0.28.13", + "typedoc-plugin-external-module-map": "^2.2.0", + "typescript": "^5.9.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz", + "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.23.0", + "@shikijs/langs": "^3.23.0", + "@shikijs/themes": "^3.23.0", + "@shikijs/types": "^3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "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/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@koa/router": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-14.0.0.tgz", + "integrity": "sha512-LBSu5K0qAaaQcXX/0WIB9PGDevyCxxpnc1uq13vV/CgObaVxuis5hKl3Eboq/8gcb6ebnkAStW9NB/Em2eYyFA==", + "deprecated": "Please upgrade to v15 or higher. All reported bugs in this version are fixed in newer releases, dependencies have been updated, and security has been improved.", + "license": "MIT", + "dependencies": { + "debug": "^4.4.1", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "path-to-regexp": "^8.2.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@schemastore/package": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@schemastore/package/-/package-0.0.6.tgz", + "integrity": "sha512-uNloNHoyHttSSdeuEkkSC+mdxJXMKlcUPOMb//qhQbIQijXg8x54VmAw3jm6GJZQ5DBtIqGBd66zEQCDCChQVA==", + "license": "MIT" + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@skeldjs/au-client": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@skeldjs/au-client/-/au-client-3.0.3.tgz", + "integrity": "sha512-ZvjcLCjQcljl2EUtPIUL5uqG1pyjvU1rgL02CxkGc6IFNW3VF+rDHJZHjjsun7gMzyoTOTP+zqTUf/7/5KQQKw==", + "license": "GPL-3.0-only" + }, + "node_modules/@skeldjs/au-constants": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@skeldjs/au-constants/-/au-constants-3.0.3.tgz", + "integrity": "sha512-A8xD3XuX5eyrw+HWuqNNrMhw1TQ78Vdq8Zypg5cgYxWe12hgpqf22X9XgOkru1M/943u1xmkQoNvASDD0/j38g==", + "license": "GPL-3.0-only" + }, + "node_modules/@skeldjs/au-core": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@skeldjs/au-core/-/au-core-3.0.3.tgz", + "integrity": "sha512-Mobo6sn8CjLDmo9DT1YTQlQMtVzuTQy9+obK5e4vVhhXSDfebl4avk6A+6l9ZW/FqgQfZOARmFpV30vZGdJ7Cg==", + "license": "GPL-3.0-only", + "dependencies": { + "@skeldjs/au-constants": "3.0.3", + "@skeldjs/au-protocol": "3.0.3", + "@skeldjs/events": "3.0.3", + "@skeldjs/hazel": "3.0.3" + } + }, + "node_modules/@skeldjs/au-protocol": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@skeldjs/au-protocol/-/au-protocol-3.0.3.tgz", + "integrity": "sha512-35KuC7ZOgfMlwn2NSfpabC0di76NAYsqjV2oWhdrmxvXcXPsOgoXx1R4K8V8dsh5Chj9odCysyjUOMbsLkI5mQ==", + "license": "GPL-3.0-only", + "dependencies": { + "@skeldjs/au-constants": "3.0.3", + "@skeldjs/hazel": "3.0.3" + } + }, + "node_modules/@skeldjs/au-text": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@skeldjs/au-text/-/au-text-3.0.3.tgz", + "integrity": "sha512-00fKMnEViEPFNOF6q+0eM7IXvYgXcqIDhZGNTbKwTLqWhhwWp0QJgXBBRws4otoAd0/jiyS/zFYq+WzA3Vh57Q==", + "license": "GPL-3.0-only" + }, + "node_modules/@skeldjs/events": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@skeldjs/events/-/events-3.0.3.tgz", + "integrity": "sha512-ZONbKbfq1aIY8JxSFWBmhxuWcn7mvvEvxM1OUO/e/+0zwQYqWCN5Jwv2u+rumXqPoerVBT1kd/I6JZ2kXaM5/A==", + "license": "GPL-3.0-only" + }, + "node_modules/@skeldjs/hazel": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@skeldjs/hazel/-/hazel-3.0.3.tgz", + "integrity": "sha512-AQCsgcJVBGXSLd1mkgyvpttL3n5QUg7919pc18gmdIWGh8XJ9yl3XoGmg5uUCZXVcpQ21RfrrdL98zEfQQyumQ==", + "license": "GPL-3.0-only" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@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/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": "*", + "@types/node": "*" + } + }, + "node_modules/@types/co-body": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz", + "integrity": "sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*" + } + }, + "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": "*" + } + }, + "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": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/formidable": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz", + "integrity": "sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.2.tgz", + "integrity": "sha512-CB+iyjjh1uS5N6/CKwXvw0qA7USMS2WVc4Tjf660yCjhdvqzNr8gdFcIawB41zGGptOQ+d1fnpaQWIIUXYxR3w==", + "license": "MIT", + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa__router": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/@types/koa__router/-/koa__router-12.0.5.tgz", + "integrity": "sha512-1HeLxuDn4n5it1yZYCSyOYXo++73zT0ffoviXnPxbwbxLbvDFEvWD9ZzpRiIpK4oKR0pi+K+Mk/ZjyROjW3HSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "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/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/prompts": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz", + "integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "kleur": "^3.0.3" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "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": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vorpal": { + "version": "1.12.8", + "resolved": "https://registry.npmjs.org/@types/vorpal/-/vorpal-1.12.8.tgz", + "integrity": "sha512-Qt+Yxa1q6QCaYMxZFXlyPOF3ktIscTelNr1AFYuKM7/Dhlki4gvc476uFyA/hYvskSA6V8W+55x9FjlbAPcYdQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/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" + } + }, + "node_modules/accepts/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" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "license": "MIT", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "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" + } + }, + "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", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-width": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", + "integrity": "sha512-eMU2akIeEIkCxGXUNmDnJq1KzOIiPnJ+rKqRe6hcxE3vIOPvpMrBYOn/Bl7zNlYJj/zQxXquAnozHUCf9Whnsg==", + "license": "ISC" + }, + "node_modules/co-body": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz", + "integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==", + "license": "MIT", + "dependencies": { + "@hapi/bourne": "^3.0.0", + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/co-body/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" + } + }, + "node_modules/co-body/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" + } + }, + "node_modules/co-body/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" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/co-body/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", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "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==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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" + } + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "license": "MIT" + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "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" + } + }, + "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/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "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", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/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/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "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" + } + }, + "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" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/formidable": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", + "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "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" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-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", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "license": "MIT", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/in-publish": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", + "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==", + "license": "ISC", + "bin": { + "in-install": "in-install.js", + "in-publish": "in-publish.js", + "not-in-install": "not-in-install.js", + "not-in-publish": "not-in-publish.js" + } + }, + "node_modules/inflation": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "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/inquirer": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.11.0.tgz", + "integrity": "sha512-LIwC+g/fJbmKhDm341+RqDIV4jPf/n3pMway9xg8Ovt6CCQo1ozXhmuKTcoNIWhWJJKsSGZP+Rnuq7JgM7mE2A==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^1.0.1", + "figures": "^1.3.5", + "lodash": "^3.3.1", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inquirer/node_modules/lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==", + "license": "MIT" + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "license": "MIT", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "license": "MIT", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/koa": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa/-/koa-3.2.1.tgz", + "integrity": "sha512-e7IpWJrnanNUroVK2taAgMxoEZvHLXdQiNjeExSu/DEIWm83jaKGBgb7tLmu2rMYpA027qFB3iLR/k3AVpFRnA==", + "license": "MIT", + "dependencies": { + "accepts": "^1.3.8", + "content-disposition": "~1.0.1", + "content-type": "^1.0.5", + "cookies": "~0.9.1", + "delegates": "^1.0.0", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.5.0", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/koa-body": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/koa-body/-/koa-body-6.0.1.tgz", + "integrity": "sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg==", + "license": "MIT", + "dependencies": { + "@types/co-body": "^6.1.0", + "@types/formidable": "^2.0.5", + "@types/koa": "^2.13.5", + "co-body": "^6.1.0", + "formidable": "^2.0.1", + "zod": "^3.19.1" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", + "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/log-update": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", + "integrity": "sha512-4vSow8gbiGnwdDNrpy1dyNaXWKSCIPop0EHdE8GrnngHoJujM3QhvHUN/igsYCgPoHo7pFOezlJ61Hlln0KHyA==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^1.0.0", + "cli-cursor": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "ISC" + }, + "node_modules/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "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" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "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" + } + }, + "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" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha512-EbrziT4s8cWPmzr47eYVW3wimS4HsvlnV5ri1xw1aR6JQo/OrJX5rkl32K/QQHdxeabJETtfeaROGhd8W7uBgg==", + "license": "ISC" + }, + "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" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-localstorage": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/node-localstorage/-/node-localstorage-0.6.0.tgz", + "integrity": "sha512-t9dKMce8qUs2KK02ZiBgzZSykUxc+5UcML7/20a62ruHwfh7+bNQvrH/auxY5gFNexTwAFdr+DbptxlLq4+7qQ==", + "engines": { + "node": ">=0.10" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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" + } + }, + "node_modules/onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==", + "license": "MIT", + "engines": { + "node": ">=0.10.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" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinst": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/pinst/-/pinst-2.1.6.tgz", + "integrity": "sha512-B4dYmf6nEXg1NpDSB+orYWvKa5Kfmz5KzWC29U59dpVM4S/+xp0ak/JMEsw04UQTNNKps7klu0BUalr343Gt9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fromentries": "^1.3.2" + }, + "bin": { + "pinst": "bin.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-registry": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/query-registry/-/query-registry-2.6.0.tgz", + "integrity": "sha512-Z5oNq7qH0g96qBTx2jAvS0X71hKP4tETtSJKEl6BdihzYqh9QKiJQBMT7qIQuzxR9lxfiso+aXCFhZ+EcAoppQ==", + "license": "MIT", + "dependencies": { + "isomorphic-unfetch": "^3.1.0", + "make-error": "^1.3.6", + "tiny-lru": "^8.0.2", + "url-join": "4.0.1", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha512-8/td4MmwUB6PkZUbV25uKz7dfrmjYWxsW8DVfibWdlHRk/l/DfHKn4pU+dfcoGLFgWOdyGCzINRQD7jn+Bv+/g==", + "license": "MIT", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "license": "Apache-2.0" + }, + "node_modules/regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-2.0.0.tgz", + "integrity": "sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==", + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==", + "license": "MIT", + "dependencies": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha512-qOX+w+IxFgpUpJfkv2oGN0+ExPs68F4sZHfaRRx4dDexAQkG83atugKVEylyT5ARees3HBbfmuvnjbrd8j9Wjw==", + "license": "MIT", + "dependencies": { + "once": "^1.3.0" + } + }, + "node_modules/rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha512-1I1+G2gteLB8Tkt8YI1sJvSIfa0lWuRtC8GjvtyPBcLSF5jBCCJJqKrpER5JU5r6Bhe+i9/pK3VMuUcXu0kdwQ==" + }, + "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.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=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/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "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" + } + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "license": "MIT", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/tiny-lru": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-8.0.2.tgz", + "integrity": "sha512-ApGvZ6vVvTNdsmt676grvCkUCGwzG9IqXma5Z07xJgiC5L7akUMof5U8G2JTI9Rz/ovtVhJBlY6mNhEvtjzOIg==", + "license": "BSD-3-Clause", + "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==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typedoc": { + "version": "0.28.19", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.19.tgz", + "integrity": "sha512-wKh+lhdmMFivMlc6vRRcMGXeGEHGU2g8a2CkPTJjJlwRf1iXbimWIPcFolCqe4E0d/FRtGszpIrsp3WLpDB8Pw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.23.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.1", + "minimatch": "^10.2.5", + "yaml": "^2.8.3" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x" + } + }, + "node_modules/typedoc-plugin-external-module-map": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-external-module-map/-/typedoc-plugin-external-module-map-2.2.0.tgz", + "integrity": "sha512-wWou92pJOMJk6cpUpbIEsBjtZKGLnu9UsVamXhndhkyBBvOBxaS+dx8USPDYByuy1Xx05V36IF+GOLqjPTpfyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.14.14" + }, + "peerDependencies": { + "typedoc": ">=0.27 <2.0" + } + }, + "node_modules/typedoc-plugin-external-module-map/node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/typedoc-plugin-external-module-map/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/typedoc/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "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/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", + "license": "MIT" + }, + "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/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "license": "MIT" + }, + "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/validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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" + } + }, + "node_modules/vorpal": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/vorpal/-/vorpal-1.12.0.tgz", + "integrity": "sha512-lYEhd75l75P3D1LKpm4KqdOSpNyNdDJ9ixEZmC5ZAZUKGy6JNexfMdQ9SNaT5pCHuzuXXRJQedJ+CdqNg/D4Kw==", + "license": "MIT", + "dependencies": { + "babel-polyfill": "^6.3.14", + "chalk": "^1.1.0", + "in-publish": "^2.0.0", + "inquirer": "0.11.0", + "lodash": "^4.5.1", + "log-update": "^1.0.2", + "minimist": "^1.2.0", + "node-localstorage": "^0.6.0", + "strip-ansi": "^3.0.0", + "wrap-ansi": "^2.0.0" + }, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.10.0" + } + }, + "node_modules/vorpal/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vorpal/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vorpal/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "license": "MIT", + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/src/Matchmaker.ts b/src/Matchmaker.ts index 91db5bdb9..3f550b039 100644 --- a/src/Matchmaker.ts +++ b/src/Matchmaker.ts @@ -7,10 +7,10 @@ import * as crypto from "crypto"; import * as KoaRouter from "@koa/router"; import { HazelWriter } from "@skeldjs/hazel"; import { RoomCode, Version } from "@skeldjs/au-client"; -import { DisconnectReason, Filters, GameKeyword, GameMap, GameMode, Platform, QuickChatMode, StringName } from "@skeldjs/au-core"; +import { DisconnectReason, Filters, GameKeyword, GameMap, GameMode, GameState, Platform, QuickChatMode, StringName } from "@skeldjs/au-core"; import { WaterwayServer } from "./WaterwayServer"; -import { Room, RoomPrivacy } from "./Room"; +import { Room, RoomPrivacy, logMaps } from "./Room"; import { Logger } from "./Logger"; export type GameListingJson = { @@ -41,6 +41,31 @@ export type GameFoundByCodeJson = { UntranslatedRegion: string; } +export type PlayerListingJson = { + ClientId: number; + Username: string; + Color: number; + HatId: string; + SkinId: string; + VisorId: string; + IsHost: boolean; + IsDead: boolean; +} + +export type GameInfoJson = GameListingJson & { + Players: PlayerListingJson[]; + GameState: string; + GameMode: string; + Privacy: string; +} + +export type FilterTagJson = { + Name: string; + DisplayName: string; + Type: string; + Count: number; +} + export type MatchmakerTokenPayload = { Content: { Puid: string; @@ -368,6 +393,252 @@ export class Matchmaker { } } + // ── Root / Home Page ── + router.get("/", async (ctx) => { + const totalPlayers = [...this.server.connections.values()].filter(c => c.room).length; + const totalConnections = this.server.connections.size; + const totalRooms = this.server.rooms.size; + + const publicRooms = [...this.server.rooms.values()] + .filter(r => r.privacy === RoomPrivacy.Public); + + const roomRows = publicRooms.slice(0, 20).map(room => { + const mapName = logMaps[room.settings.map] || GameMap[room.settings.map] || "Unknown"; + const modeName = GameMode[room.settings.gameMode] || "Normal"; + const stateName = GameState[room.gameState] || "Lobby"; + const roomAge = Math.floor((Date.now() - room.createdAt) / 1000); + const ageStr = roomAge < 60 ? `${roomAge}s` : `${Math.floor(roomAge / 60)}m`; + + return ` + ${room.code} + ${room.roomName} + ${room.players.size}/${room.settings.maxPlayers} + ${mapName} + ${modeName} + ${stateName} + ${ageStr} + `; + }).join(""); + + const html = ` + + + + + Waterway — Among Us Server + + + +
+

⚓ Waterway

+

Among Us Custom Server — v${WaterwayServer.serverVersion}

+
+
+
${totalRooms}
+
Rooms
+
+
+
${totalPlayers}
+
Players In-Game
+
+
+
${totalConnections}
+
Connections
+
+
+
${this.server.loadedPlugins.size}
+
Plugins
+
+
+
+ +
+

📡 Active Rooms

+ ${roomRows + ? ` + + + + ${roomRows} +
CodeHostPlayersMapModeStateAge
` + : `
No active rooms. Host a game to get started!
`} + +

🔌 API Endpoints

+
+
+ GET/ +
This page — server overview
+
+
+ POST/api/user +
Get a matchmaker authentication token
+
+
+ GET/api/games/:id +
Get detailed info for a specific room
+
+
+ GET/api/games/filtered +
Search and filter public rooms
+
+
+ POST/api/games +
Find host IP/port for a game code
+
+
+ GET/api/filters +
List available search filters
+
+
+ GET/api/filtertags +
Dynamic filter tags from active rooms
+
+
+
+ + + +`; + + ctx.type = "text/html"; + ctx.status = 200; + ctx.body = html; + }); + router.post("/api/user", async (ctx) => { const body = (ctx.request as any).body; if (ctx.req.headers["content-type"] !== "application/json") { @@ -713,9 +984,68 @@ export class Matchmaker { }); router.get("/api/filtertags", ctx => { - // TODO: when does this get called? + // Build dynamic filter tags from active rooms + const tags: FilterTagJson[] = []; + + // Map tags + const mapCounts: Record = {}; + const languageCounts: Record = {}; + const gameModeCounts: Record = {}; + + for (const [, room] of this.server.rooms) { + if (room.privacy === RoomPrivacy.Private && + !this.server.config.gameListing.ignorePrivacy) { + continue; + } + + const mapName = logMaps[room.settings.map] || GameMap[room.settings.map] || "Unknown"; + mapCounts[mapName] = (mapCounts[mapName] || 0) + 1; + + const langName = GameKeyword[room.settings.keywords] || "Unknown"; + languageCounts[langName] = (languageCounts[langName] || 0) + 1; + + const modeName = GameMode[room.settings.gameMode] || "Normal"; + gameModeCounts[modeName] = (gameModeCounts[modeName] || 0) + 1; + } + + // Build map filter tags + for (const [name, count] of Object.entries(mapCounts)) { + tags.push({ Name: name, DisplayName: name, Type: "map", Count: count }); + } + + // Build language filter tags + for (const [name, count] of Object.entries(languageCounts)) { + tags.push({ Name: name, DisplayName: name, Type: "language", Count: count }); + } + + // Build game mode filter tags + for (const [name, count] of Object.entries(gameModeCounts)) { + tags.push({ Name: name, DisplayName: name, Type: "gameMode", Count: count }); + } + + // Include configured static filter tags from server config + const configuredTags = this.server.config.gameListing.filterTags; + if (configuredTags && Array.isArray(configuredTags)) { + for (const tag of configuredTags) { + tags.push({ + Name: tag.name, + DisplayName: tag.displayName, + Type: "tag", + Count: 0, + }); + } + } else { + // Default tags if none configured + tags.push( + { Name: "Beginner", DisplayName: "Beginner Friendly", Type: "tag", Count: 0 }, + { Name: "Expert", DisplayName: "Expert", Type: "tag", Count: 0 }, + { Name: "Casual", DisplayName: "Casual", Type: "tag", Count: 0 }, + { Name: "Serious", DisplayName: "Serious", Type: "tag", Count: 0 }, + ); + } + ctx.status = 200; - ctx.body = []; + ctx.body = tags; }); router.get("/api/games/:game_id", ctx => { @@ -735,15 +1065,83 @@ export class Matchmaker { this.logger.info("Client found room: %s", foundRoom); + // Build enhanced player listing + const players: PlayerListingJson[] = []; + for (const [, player] of foundRoom.players) { + const playerInfo = player.getPlayerInfo(); + const connection = foundRoom.connections.get(player.clientId); + players.push({ + ClientId: player.clientId, + Username: player.username || "Unknown", + Color: playerInfo?.currentOutfit?.color ?? -1, + HatId: playerInfo?.currentOutfit?.hatId ?? "", + SkinId: playerInfo?.currentOutfit?.skinId ?? "", + VisorId: playerInfo?.currentOutfit?.visorId ?? "", + IsHost: foundRoom.authorityId === player.clientId || + (connection ? foundRoom.actingHosts.has(connection) : false), + IsDead: playerInfo?.isDead ?? false, + }); + } + + const gameListing = this.getGameListing(ctx.socket.remoteAddress || "", foundRoom); + const gameInfo: GameInfoJson = { + ...gameListing, + Players: players, + GameState: GameState[foundRoom.gameState] || "Unknown", + GameMode: GameMode[foundRoom.settings.gameMode] || "Normal", + Privacy: foundRoom.privacy === RoomPrivacy.Public ? "Public" : "Private", + }; + ctx.status = 200; ctx.body = { Errors: null, - Game: this.getGameListing(ctx.socket.remoteAddress || "", foundRoom), + Game: gameInfo, Region: StringName.NoTranslation, UntranslatedRegion: this.server.config.clusterName, } as GameFoundByCodeJson; }); + router.post("/api/games/:game_id/refresh", ctx => { + if (!this.verifyRequest(ctx)) { + ctx.status = 401; + return; + } + + const gameCode = parseInt(ctx.params.game_id); + const foundRoom = this.server.rooms.get(gameCode); + + if (!foundRoom) { + ctx.status = 404; + ctx.body = { + Errors: [{ Reason: DisconnectReason[DisconnectReason.GameNotFound] }] + }; + return; + } + + // Refresh the room's filter-related metadata + // This allows hosts to update their room's visibility tags + const body = (ctx.request as any).body || {}; + + if (typeof body.privacy === "number") { + foundRoom.privacy = body.privacy === 1 + ? RoomPrivacy.Public + : RoomPrivacy.Private; + } + + if (typeof body.roomName === "string" && body.roomName.length > 0) { + foundRoom.setRoomNameOverride(body.roomName); + } + + this.logger.info("Room %s refreshed filters (privacy=%s)", foundRoom, + foundRoom.privacy === RoomPrivacy.Public ? "public" : "private"); + + ctx.status = 200; + ctx.body = { + Success: true, + Game: this.getGameListing(ctx.socket.remoteAddress || "", foundRoom), + }; + }); + router.use((req, res) => { this.logger.info("Bad request to %s", req.url); }); diff --git a/src/Room.ts b/src/Room.ts index d313f06c0..63de9d080 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -49,6 +49,7 @@ import { EndGameIntent, GameDataMessageTag, GameMap, + GameMode, GameOverReason, GameState, Hat, @@ -83,11 +84,19 @@ import { ClientBroadcastEvent, ClientLeaveEvent, EventTarget, + PlayerLevelChangedEvent, + PlayerMurderFailEvent, + PlayerTaskCompletedEvent, + PlayerTaskProgressEvent, + PlayerTaskStartedEvent, RoomBeforeDestroyEvent, RoomCreateEvent, RoomDestroyEvent, RoomGameEndEvent, RoomGameStartEvent, + RoomHideAndSeekEndEvent, + RoomHideAndSeekStartEvent, + RoomHiderCaughtEvent, RoomSelectHostEvent, getPluginEventListeners } from "./api"; @@ -104,6 +113,9 @@ import { import { UnknownComponent } from "./components"; import { Logger } from "./Logger"; +import { GameOptionsValidator } from "./game/GameOptions"; +import { HideAndSeekManager } from "./game/modes/HideAndSeekManager"; +import { RoleManager } from "./game/roles/RoleManager"; import { fmtConfigurableLog } from "./util/fmtLogFormat"; import { Connection, logLanguages, logPlatforms } from "./Connection"; @@ -260,11 +272,19 @@ export enum RoomPrivacy { export type RoomEvents = EventMapFromList<[ ClientBroadcastEvent, ClientLeaveEvent, + PlayerLevelChangedEvent, + PlayerMurderFailEvent, + PlayerTaskCompletedEvent, + PlayerTaskProgressEvent, + PlayerTaskStartedEvent, RoomBeforeDestroyEvent, RoomCreateEvent, RoomDestroyEvent, RoomGameEndEvent, RoomGameStartEvent, + RoomHideAndSeekEndEvent, + RoomHideAndSeekStartEvent, + RoomHiderCaughtEvent, RoomSelectHostEvent ]>; @@ -314,6 +334,16 @@ export class Room extends StatefulRoom { privacy: RoomPrivacy; + /** + * The active game mode manager, if any (e.g., HideAndSeekManager). + */ + gameModeManager: HideAndSeekManager | null; + + /** + * The role manager for this room, handling role assignment and lifecycle. + */ + roleManager: RoleManager; + protected roomNameOverride: string; protected eventTargets: EventTarget[]; @@ -355,6 +385,8 @@ export class Room extends StatefulRoom { this.roomNameOverride = ""; this.eventTargets = []; + this.gameModeManager = null; + this.roleManager = new RoleManager(this); this.lastNetId = 100000; @@ -365,6 +397,18 @@ export class Room extends StatefulRoom { this.logger = new Logger(() => util.inspect(this.code, true, null, true), this.server.vorpal); + // Apply enforced settings from config (overrides host settings) + if (this.config.enforceSettings && Object.keys(this.config.enforceSettings).length > 0) { + this.settings.patch(this.config.enforceSettings); + this.logger.debug("Applied enforced settings to room"); + } + + // Apply default game mode if not set + const currentMode = this.settings.gameMode; + if (currentMode === undefined || currentMode === GameMode.None) { + (this.settings as any).gameMode = this.config.defaultGameMode || GameMode.Normal; + } + this.on("player.setname", async ev => { if (ev.oldName) { this.logger.info("%s changed their name from %s to %s", @@ -409,10 +453,36 @@ export class Room extends StatefulRoom { this.on("player.setlevel", async ev => { const connection = this.getConnection(ev.player); if (!connection) return; + + const oldLevel = connection.playerLevel; + connection.playerLevel = (ev as any).newLevel ?? ev.player.playerLevel; + + this.emit(new PlayerLevelChangedEvent( + this, + ev.player, + oldLevel, + connection.playerLevel + )); + await this.updateAuthorityForClient(this.getClientAwareAuthorityId(connection), connection); }); this.on("player.syncsettings", async ev => { + if (!this.canMakeHostChanges(ev.player as Player)) { + this.logger.warn("%s attempted to change settings but is not the host", ev.player); + return; + } + + const newSettings = (ev as any).newSettings || ev.settings; + if (newSettings) { + const validationResult = GameOptionsValidator.validateGameSettings(newSettings); + if (!validationResult.valid) { + this.logger.warn("%s sent invalid game settings: %s", + ev.player, validationResult.errors.join("; ")); + return; + } + } + if (this.config.enforceSettings) { ev.setSettings(this.config.enforceSettings); } @@ -425,9 +495,61 @@ export class Room extends StatefulRoom { this.logger.info("Meeting started (%s's body was reported)", ev.body); } }); + + this.on("player.completetask", async ev => { + const taskType = ev.task?.taskType ?? 0; + const taskId = ev.message?.taskIdx ?? 0; + this.logger.info("%s completed task (idx: %s)", + ev.player, taskId); + + // Route through role manager for role-specific behavior + this.roleManager.handleTaskComplete( + ev.player, + taskType, + taskId + ); + + this.emit(new PlayerTaskCompletedEvent( + this, + ev.player, + taskType, + taskId, + false + )); + }); + + this.on("player.checkmurder", async ev => { + if (!ev.isValid) { + this.emit(new PlayerMurderFailEvent( + this, + ev.player, + ev.victim, + "cooldown", + "Murder check determined murder is not valid" + )); + } + }); + + this.on("player.murder", async ev => { + this.logger.info("%s murdered %s", + ev.player, ev.victim); + + // Route through role manager for role-specific kill behavior (e.g., Viper) + this.roleManager.handleKill(ev.player, ev.victim); + }); + + this.on("player.die", async ev => { + // Route through role manager for role-specific death behavior (e.g., Phantom) + this.roleManager.handleDeath(ev.player); + }); } protected _reset() { + if (this.gameModeManager) { + this.gameModeManager.destroy(); + this.gameModeManager = null; + } + this.roleManager.handleGameEnd(); this.players.clear(); this.networkedObjects.clear(); this.messageStream = []; @@ -704,6 +826,15 @@ export class Room extends StatefulRoom { } async handleRpcMessage(message: RpcMessage, senderPlayer: Player) { + // Route through game mode manager first (may block certain RPCs) + if (this.gameModeManager) { + const handled = await this.gameModeManager.handleRpc(senderPlayer, message); + if (!handled) { + // Manager blocked this RPC (e.g., body report in H&S) + return true; + } + } + const component = this.networkedObjects.get(message.netId); if (component) { @@ -1411,6 +1542,12 @@ export class Room extends StatefulRoom { this.connections.delete(leavingConnection.clientId); leavingConnection.room = undefined; + // Notify game mode manager of disconnect + const leavingPlayer = this.players.get(leavingConnection.clientId) as Player | undefined; + if (leavingPlayer && this.gameModeManager) { + await this.gameModeManager.handlePlayerDisconnect(leavingPlayer); + } + await this.handleLeave(leavingConnection.clientId); if (this.connections.size === 0) { @@ -1490,10 +1627,25 @@ export class Room extends StatefulRoom { await this.broadcastImmediate([], [new StartGameMessage(this.code.id)]); + // Initialize game mode-specific manager + if ((this.settings as any).gameMode === GameMode.HideNSeek || + (this.settings as any).gameMode === GameMode.HideNSeekFools) { + this.gameModeManager = new HideAndSeekManager(this); + this.logger.info("Initialized Hide and Seek game mode manager"); + } + if (this.isAuthoritative) { await this.updateAllClientAwareAuthority(); await super.handleStartGame(); this.logger.info("Game started"); + + // Assign roles to players + this.roleManager.assignRoles(); + + // Start mode-specific logic after game is fully initialized + if (this.gameModeManager instanceof HideAndSeekManager) { + await this.gameModeManager.startGame(); + } } } diff --git a/src/WaterwayServer.ts b/src/WaterwayServer.ts index 0612247a3..63ea487c2 100644 --- a/src/WaterwayServer.ts +++ b/src/WaterwayServer.ts @@ -87,6 +87,7 @@ import { LoadedPlugin, PluginLoader, WorkerPlugin } from "./handlers"; import i18n from "./i18n"; import { Matchmaker } from "./Matchmaker"; import { Logger } from "./Logger"; +import { GameOptionsValidator } from "./game/GameOptions"; const byteSizes = ["bytes", "kb", "mb", "gb", "tb"]; function formatBytes(bytes: number) { @@ -249,6 +250,21 @@ export type LoggingConfig = { export type ValidSearchTerm = "map" | "chat" | "chatType"; +export type FilterTagConfig = { + /** + * The internal name/key for this filter tag. + */ + name: string; + /** + * The display name shown to users. + */ + displayName: string; + /** + * Optional icon identifier for the tag. + */ + icon?: string; +}; + export type GameListingConfig = { /** * Whether to ignore the privacy of a room, and return even private ones. @@ -278,6 +294,12 @@ export type GameListingConfig = { * @default false */ requireExactMatches: boolean; + /** + * Static filter tags to serve via the /api/filtertags endpoint. + * These appear as filter buttons in the client UI. + * @default [] + */ + filterTags: FilterTagConfig[]; } export type ChatCommandConfig = { @@ -373,8 +395,20 @@ export type RoomsConfig = StatefulRoomConfig & { gameCodes: "v1" | "v2"; /** * Enforce certain settings, preventing the host from changing them. + * These settings are applied on room creation and cannot be overridden. */ enforceSettings: Partial; + /** + * Allowed game modes for rooms on this server. An empty array means all + * game modes are allowed. + * @default [] + */ + allowedGameModes: number[]; + /** + * The default game mode for new rooms. + * @default 1 (Normal) + */ + defaultGameMode: number; /** * Options regarding room plugins. */ @@ -1507,6 +1541,14 @@ export class WaterwayServer extends EventEmitter { if (ev.canceled) return; + const validationResult = GameOptionsValidator.validateGameSettingsObject(message.gameSettings); + if (!validationResult.valid) { + this.logger.warn("%s created room with invalid game options: %s", + sender, validationResult.errors.join("; ")); + await sender.disconnect("Invalid game options: " + validationResult.errors.join(", ")); + return; + } + const room = await this.createRoom(ev.alteredRoomCode, message.gameSettings, sender); this.logger.info("%s created room %s", diff --git a/src/api/events/index.ts b/src/api/events/index.ts index 36ab9a3c9..72e1b0942 100644 --- a/src/api/events/index.ts +++ b/src/api/events/index.ts @@ -1,5 +1,6 @@ export * from "./adaptor"; export * from "./client"; +export * from "./player"; export * from "./room"; export * from "./worker"; diff --git a/src/api/events/player/LevelChanged.ts b/src/api/events/player/LevelChanged.ts new file mode 100644 index 000000000..6a1a830d7 --- /dev/null +++ b/src/api/events/player/LevelChanged.ts @@ -0,0 +1,30 @@ +import { BasicEvent } from "@skeldjs/events"; +import { Player } from "@skeldjs/au-core"; + +import { Room } from "../../../Room"; + +/** + * Emitted when a player's level changes (via SetLevel RPC). + */ +export class PlayerLevelChangedEvent extends BasicEvent { + static eventName = "player.levelchanged" as const; + eventName = "player.levelchanged" as const; + + constructor( + public readonly room: Room, + /** + * The player whose level changed. + */ + public readonly player: Player, + /** + * The player's previous level. + */ + public readonly oldLevel: number, + /** + * The player's new level. + */ + public readonly newLevel: number, + ) { + super(); + } +} diff --git a/src/api/events/player/MurderFail.ts b/src/api/events/player/MurderFail.ts new file mode 100644 index 000000000..3dbf393bb --- /dev/null +++ b/src/api/events/player/MurderFail.ts @@ -0,0 +1,42 @@ +import { BasicEvent } from "@skeldjs/events"; +import { Player } from "@skeldjs/au-core"; + +import { Room } from "../../../Room"; + +/** + * Emitted when an impostor's murder attempt fails. + * + * Reasons for failure include: + * - "protected": The target was protected by a Guardian Angel. + * - "already_dead": The target was already killed. + * - "invulnerable": The target is in an invulnerable state. + * - "in_vent": The target is in a vent. + * - "cooldown": The killer's kill cooldown is not ready. + * - "not_impostor": The killer is not an impostor. + */ +export class PlayerMurderFailEvent extends BasicEvent { + static eventName = "player.murderfail" as const; + eventName = "player.murderfail" as const; + + constructor( + public readonly room: Room, + /** + * The player who attempted the murder. + */ + public readonly player: Player, + /** + * The intended target of the murder. + */ + public readonly target: Player, + /** + * The reason the murder failed. + */ + public readonly reason: "protected" | "already_dead" | "invulnerable" | "in_vent" | "cooldown" | "not_impostor" | string, + /** + * Optional additional details about the failure. + */ + public readonly details?: string, + ) { + super(); + } +} diff --git a/src/api/events/player/TaskCompleted.ts b/src/api/events/player/TaskCompleted.ts new file mode 100644 index 000000000..e5687cb61 --- /dev/null +++ b/src/api/events/player/TaskCompleted.ts @@ -0,0 +1,34 @@ +import { BasicEvent } from "@skeldjs/events"; +import { Player } from "@skeldjs/au-core"; + +import { Room } from "../../../Room"; + +/** + * Emitted when a player completes a task. + */ +export class PlayerTaskCompletedEvent extends BasicEvent { + static eventName = "player.taskcompleted" as const; + eventName = "player.taskcompleted" as const; + + constructor( + public readonly room: Room, + /** + * The player who completed the task. + */ + public readonly player: Player, + /** + * The type of task (from TaskType enum). + */ + public readonly taskType: number, + /** + * The unique task ID. + */ + public readonly taskId: number, + /** + * Whether this was a long/multi-step task. + */ + public readonly isLongTask: boolean, + ) { + super(); + } +} diff --git a/src/api/events/player/TaskProgress.ts b/src/api/events/player/TaskProgress.ts new file mode 100644 index 000000000..35c302261 --- /dev/null +++ b/src/api/events/player/TaskProgress.ts @@ -0,0 +1,30 @@ +import { BasicEvent } from "@skeldjs/events"; +import { Player } from "@skeldjs/au-core"; + +import { Room } from "../../../Room"; + +/** + * Emitted when a player's task progress updates. + */ +export class PlayerTaskProgressEvent extends BasicEvent { + static eventName = "player.taskprogress" as const; + eventName = "player.taskprogress" as const; + + constructor( + public readonly room: Room, + /** + * The player whose task progressed. + */ + public readonly player: Player, + /** + * The unique task ID. + */ + public readonly taskId: number, + /** + * The current progress value (0-100 or game-specific). + */ + public readonly progress: number, + ) { + super(); + } +} diff --git a/src/api/events/player/TaskStarted.ts b/src/api/events/player/TaskStarted.ts new file mode 100644 index 000000000..cd8162b02 --- /dev/null +++ b/src/api/events/player/TaskStarted.ts @@ -0,0 +1,30 @@ +import { BasicEvent } from "@skeldjs/events"; +import { Player } from "@skeldjs/au-core"; + +import { Room } from "../../../Room"; + +/** + * Emitted when a player starts a task. + */ +export class PlayerTaskStartedEvent extends BasicEvent { + static eventName = "player.taskstarted" as const; + eventName = "player.taskstarted" as const; + + constructor( + public readonly room: Room, + /** + * The player who started the task. + */ + public readonly player: Player, + /** + * The type of task (from TaskType enum). + */ + public readonly taskType: number, + /** + * The unique task ID. + */ + public readonly taskId: number, + ) { + super(); + } +} diff --git a/src/api/events/player/index.ts b/src/api/events/player/index.ts new file mode 100644 index 000000000..1f76b1778 --- /dev/null +++ b/src/api/events/player/index.ts @@ -0,0 +1,5 @@ +export * from "./LevelChanged"; +export * from "./MurderFail"; +export * from "./TaskCompleted"; +export * from "./TaskProgress"; +export * from "./TaskStarted"; diff --git a/src/api/events/room/HideAndSeekEnd.ts b/src/api/events/room/HideAndSeekEnd.ts new file mode 100644 index 000000000..66187dbab --- /dev/null +++ b/src/api/events/room/HideAndSeekEnd.ts @@ -0,0 +1,28 @@ +import { BasicEvent } from "@skeldjs/events"; + +import { Room } from "../../../Room"; + +/** + * Emitted when a Hide and Seek game ends. + */ +export class RoomHideAndSeekEndEvent extends BasicEvent { + static eventName = "room.hideandseekend" as const; + eventName = "room.hideandseekend" as const; + + constructor( + public readonly room: Room, + /** + * Who won the game. + * - "seekers": The seeker killed all hiders. + * - "hiders": The hiders completed all tasks or survived the timer. + * - "none": The game ended without a winner (e.g., destroyed). + */ + public readonly winner: "seekers" | "hiders" | "none", + /** + * The reason the game ended, for display/logging. + */ + public readonly reason: string, + ) { + super(); + } +} diff --git a/src/api/events/room/HideAndSeekStart.ts b/src/api/events/room/HideAndSeekStart.ts new file mode 100644 index 000000000..1fb66199f --- /dev/null +++ b/src/api/events/room/HideAndSeekStart.ts @@ -0,0 +1,31 @@ +import { BasicEvent } from "@skeldjs/events"; +import { Player } from "@skeldjs/au-core"; + +import { Room } from "../../../Room"; + +/** + * Emitted when a Hide and Seek game starts. + * Contains information about the seeker, hiders, and hide duration. + */ +export class RoomHideAndSeekStartEvent extends BasicEvent { + static eventName = "room.hideandseekstart" as const; + eventName = "room.hideandseekstart" as const; + + constructor( + public readonly room: Room, + /** + * The player selected as the seeker (impostor role). + */ + public readonly seekerPlayer: Player, + /** + * All players who are hiders (crewmate roles). + */ + public readonly hiderPlayers: Player[], + /** + * The duration in seconds that hiders have to hide. + */ + public readonly hideDuration: number, + ) { + super(); + } +} diff --git a/src/api/events/room/HiderCaught.ts b/src/api/events/room/HiderCaught.ts new file mode 100644 index 000000000..c733366d9 --- /dev/null +++ b/src/api/events/room/HiderCaught.ts @@ -0,0 +1,30 @@ +import { BasicEvent } from "@skeldjs/events"; +import { Player } from "@skeldjs/au-core"; + +import { Room } from "../../../Room"; + +/** + * Emitted when a hider is caught (killed) by the seeker in Hide and Seek mode. + */ +export class RoomHiderCaughtEvent extends BasicEvent { + static eventName = "room.hidercaught" as const; + eventName = "room.hidercaught" as const; + + constructor( + public readonly room: Room, + /** + * The hider player that was caught. + */ + public readonly hiderPlayer: Player, + /** + * The seeker player that caught the hider. + */ + public readonly seekerPlayer: Player, + /** + * How many hiders remain alive. + */ + public readonly remainingHiders: number, + ) { + super(); + } +} diff --git a/src/api/events/room/index.ts b/src/api/events/room/index.ts index 7d3ff078f..858a7a17d 100644 --- a/src/api/events/room/index.ts +++ b/src/api/events/room/index.ts @@ -4,4 +4,7 @@ export * from "./Create"; export * from "./Destroy"; export * from "./GameEnd"; export * from "./GameStart"; +export * from "./HideAndSeekEnd"; +export * from "./HideAndSeekStart"; +export * from "./HiderCaught"; export * from "./SelectHost"; diff --git a/src/game/GameOptions.ts b/src/game/GameOptions.ts new file mode 100644 index 000000000..f9d082dbb --- /dev/null +++ b/src/game/GameOptions.ts @@ -0,0 +1,595 @@ +import { + AllGameSettings, + AllRoleSettings, + GameSettings, + RoleSettings +} from "@skeldjs/au-protocol"; + +import { + GameMap, + GameKeyword, + GameMode, + KillDistance, + RoleType, + RulesPreset, + SpecialGameModes, + TaskBarMode, +} from "@skeldjs/au-core"; + +/** + * Result of validating game options. An empty errors array means valid. + */ +export interface ValidationResult { + valid: boolean; + errors: string[]; +} + +/** + * Valid ranges for numeric game options. + */ +export const GAME_OPTION_RANGES = { + playerSpeed: { min: 0.5, max: 3.0 }, + crewmateVision: { min: 0.0, max: 5.0 }, + impostorVision: { min: 0.0, max: 5.0 }, + killCooldown: { min: 2.5, max: 60.0 }, + commonTasks: { min: 0, max: 4 }, + longTasks: { min: 0, max: 15 }, + shortTasks: { min: 0, max: 23 }, + numEmergencies: { min: 0, max: 10 }, + numImpostors: { min: 1, max: 3 }, + maxPlayers: { min: 4, max: 15 }, + discussionTime: { min: 0, max: 300 }, + votingTime: { min: 0, max: 300 }, + emergencyCooldown: { min: 0, max: 60 }, + crewmateVentUses: { min: 0, max: 30 }, + hidingTime: { min: 10, max: 600 }, + crewmateFlashlightSize: { min: 0.0, max: 5.0 }, + impostorFlashlightSize: { min: 0.0, max: 5.0 }, + finalHideTime: { min: 0, max: 300 }, + finalSeekerSpeed: { min: 0.5, max: 3.0 }, + maxPingTime: { min: 0, max: 30 }, + crewmateTimeInVent: { min: 0, max: 60 }, +} as const; + +/** + * Valid ranges for role-specific settings. + */ +export const ROLE_OPTION_RANGES: Record = { + scientistCooldown: { min: 5, max: 60 }, + scientistBatteryCharge: { min: 5, max: 30 }, + engineerCooldown: { min: 5, max: 60 }, + engineerInVentMaxTime: { min: 0, max: 60 }, + guardianAngelCooldown: { min: 35, max: 120 }, + guardianAngelPotectionDuration: { min: 5, max: 30 }, + shapeshifterCooldown: { min: 5, max: 60 }, + shapeshiftDuration: { min: 5, max: 30 }, + noisemakerAlertDuration: { min: 1, max: 30 }, + phantomCooldown: { min: 5, max: 60 }, + phantomDuration: { min: 5, max: 60 }, + trackerCooldown: { min: 5, max: 60 }, + trackerDuration: { min: 5, max: 60 }, + trackerDelay: { min: 1, max: 10 }, + detectiveSuspectLimit: { min: 1, max: 10 }, + viperDissolveTime: { min: 5, max: 60 }, +}; + +/** + * Maximum number of players that can have a given role. + */ +const MAX_ROLE_PLAYERS = 15; + +/** + * All valid game map values. + */ +const VALID_GAME_MAPS: GameMap[] = [ + GameMap.TheSkeld, + GameMap.MiraHQ, + GameMap.Polus, + GameMap.AprilFoolsTheSkeld, + GameMap.Airship, + GameMap.Fungle, +]; + +/** + * All valid kill distance values. + */ +const VALID_KILL_DISTANCES: KillDistance[] = [ + KillDistance.Short, + KillDistance.Medium, + KillDistance.Long, +]; + +/** + * All valid task bar update modes. + */ +const VALID_TASKBAR_MODES: TaskBarMode[] = [ + TaskBarMode.Normal, + TaskBarMode.MeetingOnly, + TaskBarMode.Invisible, +]; + +/** + * All valid game modes. + */ +const VALID_GAME_MODES: GameMode[] = [ + GameMode.None, + GameMode.Normal, + GameMode.HideNSeek, + GameMode.NormalFools, + GameMode.HideNSeekFools, +]; + +/** + * All valid special game modes. + */ +const VALID_SPECIAL_MODES: SpecialGameModes[] = [ + SpecialGameModes.None, + SpecialGameModes.AprilFools, +]; + +/** + * All valid rules presets. + */ +const VALID_RULES_PRESETS: RulesPreset[] = [ + RulesPreset.Custom, + RulesPreset.Standard, +]; + +/** + * Validator class for game options. + * + * Provides static methods to validate individual settings and full game settings objects. + */ +export class GameOptionsValidator { + /** + * Check if a numeric value is within a valid range. + */ + static inRange(value: number, min: number, max: number): boolean { + return value >= min && value <= max; + } + + /** + * Check if a value is a valid member of an enum-like set. + */ + static inEnum(value: T, validValues: readonly T[]): boolean { + return validValues.includes(value); + } + + /** + * Validate role chance settings (maxPlayers and chance percentages). + */ + static validateRoleChances(roleSettings: AllRoleSettings): string[] { + const errors: string[] = []; + + if (!roleSettings.roleChances) return errors; + + const roleChances = Object.entries(roleSettings.roleChances); + for (const [roleTypeStr, roleChance] of roleChances) { + const roleType = parseInt(roleTypeStr); + + if (isNaN(roleType) || !(roleType in RoleType)) { + errors.push(`Invalid role type: ${roleTypeStr}`); + continue; + } + + if (roleChance.maxPlayers < 0 || roleChance.maxPlayers > MAX_ROLE_PLAYERS) { + errors.push( + `${RoleType[roleType]}: maxPlayers (${roleChance.maxPlayers}) must be between 0 and ${MAX_ROLE_PLAYERS}` + ); + } + + if (roleChance.chance < 0 || roleChance.chance > 100) { + errors.push( + `${RoleType[roleType]}: chance (${roleChance.chance}) must be between 0 and 100` + ); + } + } + + return errors; + } + + /** + * Validate numeric role settings against defined ranges. + */ + static validateRoleSettingRanges(roleSettings: AllRoleSettings): string[] { + const errors: string[] = []; + + for (const [key, range] of Object.entries(ROLE_OPTION_RANGES)) { + const value = (roleSettings as any)[key]; + if (value !== undefined && !this.inRange(value, range.min, range.max)) { + errors.push( + `roleSettings.${key} (${value}) must be between ${range.min} and ${range.max}` + ); + } + } + + return errors; + } + + /** + * Validate all role settings. + */ + static validateRoleSettings(roleSettings: AllRoleSettings): string[] { + const errors: string[] = []; + + if (!roleSettings) return errors; + + errors.push(...this.validateRoleChances(roleSettings)); + errors.push(...this.validateRoleSettingRanges(roleSettings)); + + return errors; + } + + /** + * Validate settings specific to Normal game mode. + */ + static validateNormalModeSettings(settings: Partial): string[] { + const errors: string[] = []; + + if (settings.killCooldown !== undefined && + !this.inRange(settings.killCooldown, GAME_OPTION_RANGES.killCooldown.min, GAME_OPTION_RANGES.killCooldown.max)) { + errors.push( + `killCooldown (${settings.killCooldown}) must be between ${GAME_OPTION_RANGES.killCooldown.min} and ${GAME_OPTION_RANGES.killCooldown.max}` + ); + } + + if (settings.numImpostors !== undefined && + !this.inRange(settings.numImpostors, GAME_OPTION_RANGES.numImpostors.min, GAME_OPTION_RANGES.numImpostors.max)) { + errors.push( + `numImpostors (${settings.numImpostors}) must be between ${GAME_OPTION_RANGES.numImpostors.min} and ${GAME_OPTION_RANGES.numImpostors.max}` + ); + } + + if (settings.killDistance !== undefined && + !this.inEnum(settings.killDistance, VALID_KILL_DISTANCES)) { + errors.push(`killDistance (${settings.killDistance}) is not a valid value`); + } + + if (settings.discussionTime !== undefined && + !this.inRange(settings.discussionTime, GAME_OPTION_RANGES.discussionTime.min, GAME_OPTION_RANGES.discussionTime.max)) { + errors.push( + `discussionTime (${settings.discussionTime}) must be between 0 and 300` + ); + } + + if (settings.votingTime !== undefined && + !this.inRange(settings.votingTime, GAME_OPTION_RANGES.votingTime.min, GAME_OPTION_RANGES.votingTime.max)) { + errors.push( + `votingTime (${settings.votingTime}) must be between 0 and 300` + ); + } + + if (settings.emergencyCooldown !== undefined && + !this.inRange(settings.emergencyCooldown, GAME_OPTION_RANGES.emergencyCooldown.min, GAME_OPTION_RANGES.emergencyCooldown.max)) { + errors.push( + `emergencyCooldown (${settings.emergencyCooldown}) must be between 0 and 60` + ); + } + + return errors; + } + + /** + * Validate settings specific to Hide and Seek game mode. + */ + static validateHideAndSeekSettings(settings: Partial): string[] { + const errors: string[] = []; + + if (settings.crewmateVentUses !== undefined && + !this.inRange(settings.crewmateVentUses, GAME_OPTION_RANGES.crewmateVentUses.min, GAME_OPTION_RANGES.crewmateVentUses.max)) { + errors.push( + `crewmateVentUses (${settings.crewmateVentUses}) must be between ${GAME_OPTION_RANGES.crewmateVentUses.min} and ${GAME_OPTION_RANGES.crewmateVentUses.max}` + ); + } + + if (settings.hidingTime !== undefined && + !this.inRange(settings.hidingTime, GAME_OPTION_RANGES.hidingTime.min, GAME_OPTION_RANGES.hidingTime.max)) { + errors.push( + `hidingTime (${settings.hidingTime}) must be between ${GAME_OPTION_RANGES.hidingTime.min} and ${GAME_OPTION_RANGES.hidingTime.max}` + ); + } + + if (settings.crewmateFlashlightSize !== undefined && + !this.inRange(settings.crewmateFlashlightSize, GAME_OPTION_RANGES.crewmateFlashlightSize.min, GAME_OPTION_RANGES.crewmateFlashlightSize.max)) { + errors.push( + `crewmateFlashlightSize (${settings.crewmateFlashlightSize}) must be between 0.0 and 5.0` + ); + } + + if (settings.impostorFlashlightSize !== undefined && + !this.inRange(settings.impostorFlashlightSize, GAME_OPTION_RANGES.impostorFlashlightSize.min, GAME_OPTION_RANGES.impostorFlashlightSize.max)) { + errors.push( + `impostorFlashlightSize (${settings.impostorFlashlightSize}) must be between 0.0 and 5.0` + ); + } + + if (settings.finalHideTime !== undefined && + !this.inRange(settings.finalHideTime, GAME_OPTION_RANGES.finalHideTime.min, GAME_OPTION_RANGES.finalHideTime.max)) { + errors.push( + `finalHideTime (${settings.finalHideTime}) must be between 0 and 300` + ); + } + + if (settings.finalSeekerSpeed !== undefined && + !this.inRange(settings.finalSeekerSpeed, GAME_OPTION_RANGES.finalSeekerSpeed.min, GAME_OPTION_RANGES.finalSeekerSpeed.max)) { + errors.push( + `finalSeekerSpeed (${settings.finalSeekerSpeed}) must be between 0.5 and 3.0` + ); + } + + if (settings.maxPingTime !== undefined && + !this.inRange(settings.maxPingTime, GAME_OPTION_RANGES.maxPingTime.min, GAME_OPTION_RANGES.maxPingTime.max)) { + errors.push( + `maxPingTime (${settings.maxPingTime}) must be between 0 and 30` + ); + } + + if (settings.crewmateTimeInVent !== undefined && + !this.inRange(settings.crewmateTimeInVent, GAME_OPTION_RANGES.crewmateTimeInVent.min, GAME_OPTION_RANGES.crewmateTimeInVent.max)) { + errors.push( + `crewmateTimeInVent (${settings.crewmateTimeInVent}) must be between 0 and 60` + ); + } + + return errors; + } + + /** + * Validate common settings that apply to all game modes. + */ + static validateCommonSettings(settings: Partial): string[] { + const errors: string[] = []; + + // Validate maxPlayers + if (settings.maxPlayers !== undefined && + !this.inRange(settings.maxPlayers, GAME_OPTION_RANGES.maxPlayers.min, GAME_OPTION_RANGES.maxPlayers.max)) { + errors.push( + `maxPlayers (${settings.maxPlayers}) must be between ${GAME_OPTION_RANGES.maxPlayers.min} and ${GAME_OPTION_RANGES.maxPlayers.max}` + ); + } + + // Validate map + if (settings.map !== undefined && !this.inEnum(settings.map, VALID_GAME_MAPS)) { + errors.push(`map (${settings.map}) is not a valid game map`); + } + + // Validate player speed + if (settings.playerSpeed !== undefined && + !this.inRange(settings.playerSpeed, GAME_OPTION_RANGES.playerSpeed.min, GAME_OPTION_RANGES.playerSpeed.max)) { + errors.push( + `playerSpeed (${settings.playerSpeed}) must be between ${GAME_OPTION_RANGES.playerSpeed.min} and ${GAME_OPTION_RANGES.playerSpeed.max}` + ); + } + + // Validate vision ranges + if (settings.crewmateVision !== undefined && + !this.inRange(settings.crewmateVision, GAME_OPTION_RANGES.crewmateVision.min, GAME_OPTION_RANGES.crewmateVision.max)) { + errors.push( + `crewmateVision (${settings.crewmateVision}) must be between 0.0 and 5.0` + ); + } + + if (settings.impostorVision !== undefined && + !this.inRange(settings.impostorVision, GAME_OPTION_RANGES.impostorVision.min, GAME_OPTION_RANGES.impostorVision.max)) { + errors.push( + `impostorVision (${settings.impostorVision}) must be between 0.0 and 5.0` + ); + } + + // Validate task counts + if (settings.commonTasks !== undefined && + !this.inRange(settings.commonTasks, GAME_OPTION_RANGES.commonTasks.min, GAME_OPTION_RANGES.commonTasks.max)) { + errors.push( + `commonTasks (${settings.commonTasks}) must be between 0 and 4` + ); + } + + if (settings.longTasks !== undefined && + !this.inRange(settings.longTasks, GAME_OPTION_RANGES.longTasks.min, GAME_OPTION_RANGES.longTasks.max)) { + errors.push( + `longTasks (${settings.longTasks}) must be between 0 and 15` + ); + } + + if (settings.shortTasks !== undefined && + !this.inRange(settings.shortTasks, GAME_OPTION_RANGES.shortTasks.min, GAME_OPTION_RANGES.shortTasks.max)) { + errors.push( + `shortTasks (${settings.shortTasks}) must be between 0 and 23` + ); + } + + // Validate numEmergencies + if (settings.numEmergencies !== undefined && + !this.inRange(settings.numEmergencies, GAME_OPTION_RANGES.numEmergencies.min, GAME_OPTION_RANGES.numEmergencies.max)) { + errors.push( + `numEmergencies (${settings.numEmergencies}) must be between 0 and 10` + ); + } + + // Validate dependency: numEmergencies should not exceed maxPlayers + const maxPlayers = settings.maxPlayers ?? 10; + const numEmergencies = settings.numEmergencies; + if (numEmergencies !== undefined && maxPlayers !== undefined && numEmergencies > maxPlayers) { + errors.push( + `numEmergencies (${numEmergencies}) cannot exceed maxPlayers (${maxPlayers})` + ); + } + + // Validate game mode (on the GameSettings object, not in AllGameSettings) + if ((settings as any).gameMode !== undefined && !this.inEnum((settings as any).gameMode, VALID_GAME_MODES)) { + errors.push(`gameMode (${(settings as any).gameMode}) is not a valid game mode`); + } + + // Validate special mode + if ((settings as any).specialMode !== undefined && !this.inEnum((settings as any).specialMode, VALID_SPECIAL_MODES)) { + errors.push(`specialMode (${(settings as any).specialMode}) is not a valid special mode`); + } + + // Validate rules preset + if ((settings as any).rulesPreset !== undefined && !this.inEnum((settings as any).rulesPreset, VALID_RULES_PRESETS)) { + errors.push(`rulesPreset (${(settings as any).rulesPreset}) is not a valid rules preset`); + } + + // Validate task bar updates + if (settings.taskbarUpdates !== undefined && !this.inEnum(settings.taskbarUpdates, VALID_TASKBAR_MODES)) { + errors.push(`taskbarUpdates (${settings.taskbarUpdates}) is not a valid task bar mode`); + } + + return errors; + } + + /** + * Perform a full validation of game settings. + * + * @param settings The game settings to validate. + * @returns A {@link ValidationResult} with valid flag and array of error messages. + */ + static validateGameSettings(settings: Partial): ValidationResult { + // NOTE: When @skeldjs/au-protocol is updated with GameSettings.validate(), + // this method should delegate to it. For now, Waterway maintains its own + // validation that mirrors the SkeldJS protocol-level checks. + + const errors: string[] = []; + + errors.push(...this.validateCommonSettings(settings)); + + const gameMode = (settings as any).gameMode ?? GameMode.Normal; + switch (gameMode) { + case GameMode.Normal: + case GameMode.NormalFools: + errors.push(...this.validateNormalModeSettings(settings)); + break; + case GameMode.HideNSeek: + case GameMode.HideNSeekFools: + errors.push(...this.validateHideAndSeekSettings(settings)); + break; + } + + if (settings.roleSettings) { + errors.push(...this.validateRoleSettings(settings.roleSettings as AllRoleSettings)); + } + + return { + valid: errors.length === 0, + errors, + }; + } + + /** + * Validate a complete GameSettings object. + * Convenience method that calls validateGameSettings with the settings data. + */ + static validateGameSettingsObject(settings: GameSettings): ValidationResult { + return this.validateGameSettings(settings as any); + } + + /** + * Check whether role-specific settings have valid values for the given role type. + */ + static validateSingleRole(roleType: RoleType, roleSettings: AllRoleSettings): string[] { + const errors: string[] = []; + + switch (roleType) { + case RoleType.Scientist: + if (!this.inRange(roleSettings.scientistCooldown, + ROLE_OPTION_RANGES.scientistCooldown.min, + ROLE_OPTION_RANGES.scientistCooldown.max)) { + errors.push(`Scientist cooldown must be between 5 and 60`); + } + if (!this.inRange(roleSettings.scientistBatteryCharge, + ROLE_OPTION_RANGES.scientistBatteryCharge.min, + ROLE_OPTION_RANGES.scientistBatteryCharge.max)) { + errors.push(`Scientist battery charge must be between 5 and 30`); + } + break; + case RoleType.Engineer: + if (!this.inRange(roleSettings.engineerCooldown, + ROLE_OPTION_RANGES.engineerCooldown.min, + ROLE_OPTION_RANGES.engineerCooldown.max)) { + errors.push(`Engineer cooldown must be between 5 and 60`); + } + if (!this.inRange(roleSettings.engineerInVentMaxTime, + ROLE_OPTION_RANGES.engineerInVentMaxTime.min, + ROLE_OPTION_RANGES.engineerInVentMaxTime.max)) { + errors.push(`Engineer vent time must be between 0 and 60`); + } + break; + case RoleType.GuardianAngel: + if (!this.inRange(roleSettings.guardianAngelCooldown, + ROLE_OPTION_RANGES.guardianAngelCooldown.min, + ROLE_OPTION_RANGES.guardianAngelCooldown.max)) { + errors.push(`Guardian Angel cooldown must be between 35 and 120`); + } + if (!this.inRange(roleSettings.guardianAngelPotectionDuration, + ROLE_OPTION_RANGES.guardianAngelPotectionDuration.min, + ROLE_OPTION_RANGES.guardianAngelPotectionDuration.max)) { + errors.push(`Guardian Angel protection duration must be between 5 and 30`); + } + break; + case RoleType.Shapeshifter: + if (!this.inRange(roleSettings.shapeshifterCooldown, + ROLE_OPTION_RANGES.shapeshifterCooldown.min, + ROLE_OPTION_RANGES.shapeshifterCooldown.max)) { + errors.push(`Shapeshifter cooldown must be between 5 and 60`); + } + if (!this.inRange(roleSettings.shapeshiftDuration, + ROLE_OPTION_RANGES.shapeshiftDuration.min, + ROLE_OPTION_RANGES.shapeshiftDuration.max)) { + errors.push(`Shapeshift duration must be between 5 and 30`); + } + break; + case RoleType.Noisemaker: + if (!this.inRange(roleSettings.noisemakerAlertDuration, + ROLE_OPTION_RANGES.noisemakerAlertDuration.min, + ROLE_OPTION_RANGES.noisemakerAlertDuration.max)) { + errors.push(`Noisemaker alert duration must be between 1 and 30`); + } + break; + case RoleType.Phantom: + if (!this.inRange(roleSettings.phantomCooldown, + ROLE_OPTION_RANGES.phantomCooldown.min, + ROLE_OPTION_RANGES.phantomCooldown.max)) { + errors.push(`Phantom cooldown must be between 5 and 60`); + } + if (!this.inRange(roleSettings.phantomDuration, + ROLE_OPTION_RANGES.phantomDuration.min, + ROLE_OPTION_RANGES.phantomDuration.max)) { + errors.push(`Phantom duration must be between 5 and 60`); + } + break; + case RoleType.Tracker: + if (!this.inRange(roleSettings.trackerCooldown, + ROLE_OPTION_RANGES.trackerCooldown.min, + ROLE_OPTION_RANGES.trackerCooldown.max)) { + errors.push(`Tracker cooldown must be between 5 and 60`); + } + if (!this.inRange(roleSettings.trackerDuration, + ROLE_OPTION_RANGES.trackerDuration.min, + ROLE_OPTION_RANGES.trackerDuration.max)) { + errors.push(`Tracker duration must be between 5 and 60`); + } + if (!this.inRange(roleSettings.trackerDelay, + ROLE_OPTION_RANGES.trackerDelay.min, + ROLE_OPTION_RANGES.trackerDelay.max)) { + errors.push(`Tracker delay must be between 1 and 10`); + } + break; + case RoleType.Detective: + if (!this.inRange(roleSettings.detectiveSuspectLimit, + ROLE_OPTION_RANGES.detectiveSuspectLimit.min, + ROLE_OPTION_RANGES.detectiveSuspectLimit.max)) { + errors.push(`Detective suspect limit must be between 1 and 10`); + } + break; + case RoleType.Viper: + if (!this.inRange(roleSettings.viperDissolveTime, + ROLE_OPTION_RANGES.viperDissolveTime.min, + ROLE_OPTION_RANGES.viperDissolveTime.max)) { + errors.push(`Viper dissolve time must be between 5 and 60`); + } + break; + } + + return errors; + } +} diff --git a/src/game/index.ts b/src/game/index.ts new file mode 100644 index 000000000..cb1b91e93 --- /dev/null +++ b/src/game/index.ts @@ -0,0 +1 @@ +export * from "./GameOptions"; diff --git a/src/game/modes/HideAndSeekManager.ts b/src/game/modes/HideAndSeekManager.ts new file mode 100644 index 000000000..114428de9 --- /dev/null +++ b/src/game/modes/HideAndSeekManager.ts @@ -0,0 +1,472 @@ +import { + GameMap, + GameMode, + GameOverReason, + GameState, + Player, + RoleType, + RpcMessageTag, +} from "@skeldjs/au-core"; + +import { + RpcMessage, + MurderPlayerMessage, + ReportDeadBodyMessage, + CompleteTaskMessage, + StartGameMessage, + EndGameMessage, + GameDataMessage, +} from "@skeldjs/au-protocol"; + +import { Room, RoomPrivacy } from "../../Room"; +import { RoomHideAndSeekEndEvent, RoomHideAndSeekStartEvent, RoomHiderCaughtEvent } from "../../api"; +import { Logger } from "../../Logger"; + +/** + * Phases of a Hide and Seek game. + */ +export enum HideAndSeekPhase { + /** Game has not started yet (lobby). */ + Lobby, + /** Hiders are hiding, seeker is waiting/blinded. */ + Hiding, + /** Seeker is actively hunting hiders. */ + Seeking, + /** Final hide countdown is active, seeker has speed/ping advantage. */ + FinalHide, + /** Game has ended. */ + Ended, +} + +/** + * Manages the Hide and Seek game mode lifecycle. + * + * Game flow: + * 1. Game starts → Hiding phase (hiders hide, seeker waits) + * 2. Hiding timer expires → Seeking phase (seeker hunts) + * 3. Final hide timer (optional) → FinalHide phase (seeker gets advantages) + * 4. All hiders dead OR all tasks done → Game ends + */ +export class HideAndSeekManager { + /** Current phase of the game. */ + phase: HideAndSeekPhase = HideAndSeekPhase.Lobby; + + /** The player acting as the seeker (impostor). */ + seeker: Player | null = null; + + /** All hider players (crewmates). */ + hiders: Player[] = []; + + /** Timer handles for phase transitions. */ + private _hideTimer: NodeJS.Timeout | null = null; + private _finalHideTimer: NodeJS.Timeout | null = null; + private _finalHideCheckTimer: NodeJS.Timeout | null = null; + + /** Track completed tasks for win condition. */ + private _totalTasks: number = 0; + private _completedTasks: Set = new Set(); + + /** Logger for this manager. */ + logger: Logger; + + constructor( + public readonly room: Room, + ) { + this.logger = new Logger(() => "HideAndSeek", room.server.vorpal); + } + + /** + * Initialize and start the Hide and Seek game. + */ + async startGame() { + const settings = this.room.settings; + + // Select seeker + this.selectSeeker(); + + if (!this.seeker) { + this.logger.error("Could not select a seeker for Hide and Seek game"); + await this.room.handleEndGame(GameOverReason.ImpostorDisconnect); + return; + } + + // All other players are hiders + this.hiders = []; + for (const [, player] of this.room.players) { + if (player.clientId !== this.seeker.clientId) { + this.hiders.push(player); + } + } + + if (this.hiders.length === 0) { + this.logger.error("No hiders available for Hide and Seek game"); + await this.room.handleEndGame(GameOverReason.ImpostorDisconnect); + return; + } + + // Roles are assigned by the RoleManager when the game starts + // Seekers and hiders are already handled via the game mode logic + + // Start the hiding phase + this.phase = HideAndSeekPhase.Hiding; + + const hideDuration = settings.hidingTime * 1000; // Convert seconds to ms + + this.logger.info("Hide and Seek started: %s is the seeker, %s hiders. Hiding for %ss", + this.seeker, this.hiders.length, settings.hidingTime); + + // Calculate total tasks for win condition + this._totalTasks = this.computeTotalTasks(); + + // Emit event + await this.room.emit(new RoomHideAndSeekStartEvent( + this.room, + this.seeker, + this.hiders, + settings.hidingTime + )); + + // Send chat message + this.room.sendChat( + `Hide and Seek! ${this.seeker.username || "Someone"} is the seeker. ` + + `Hide for ${settings.hidingTime} seconds!` + ); + + // Set timer to end hiding phase + this._hideTimer = setTimeout(() => { + this.startSeekingPhase(); + }, hideDuration); + } + + /** + * Select the seeker from the players. + * Uses the configured seekerPlayerId if valid, otherwise picks randomly. + */ + private selectSeeker() { + const settings = this.room.settings; + + // If a specific seeker was configured and they exist, use them + if (settings.seekerPlayerId !== 0xffffffff) { + const configuredSeeker = this.room.players.get(settings.seekerPlayerId); + if (configuredSeeker && configuredSeeker.characterControl) { + this.seeker = configuredSeeker; + return; + } + } + + // Otherwise pick a random player + const playerArray = [...this.room.players.values()].filter( + p => p.characterControl && p.inScene + ); + + if (playerArray.length === 0) { + this.seeker = null; + return; + } + + const randomIndex = Math.floor(Math.random() * playerArray.length); + this.seeker = playerArray[randomIndex]; + } + + /** + * Transition from Hiding phase to Seeking phase. + */ + private async startSeekingPhase() { + if (this.phase === HideAndSeekPhase.Ended) return; + + this.phase = HideAndSeekPhase.Seeking; + this.logger.info("Hide and Seek: Seeking phase started"); + + // Enable flashlight if configured + if (this.room.settings.useFlashlight) { + // Flashlight behavior is handled by the client based on settings + } + + this.room.sendChat( + "The seeker is now hunting! Run and finish your tasks!" + ); + + // Check if final hide is enabled + if (this.room.settings.finalHideSeekMap && this.room.settings.finalHideTime > 0) { + const seekingDuration = Math.max( + (this.room.settings.hidingTime * 1000) - this.room.settings.finalHideTime * 1000, + 30000 // At least 30 seconds of seeking + ); + + this._finalHideTimer = setTimeout(() => { + this.startFinalHidePhase(); + }, seekingDuration); + } + } + + /** + * Transition to Final Hide phase. + */ + private async startFinalHidePhase() { + if (this.phase === HideAndSeekPhase.Ended) return; + + this.phase = HideAndSeekPhase.FinalHide; + this.logger.info("Hide and Seek: Final Hide phase started"); + + // Seeker gets speed boost + if (this.seeker?.characterControl) { + // Final seeker speed is applied via settings + } + + // Ping mechanic + if (this.room.settings.finalHidePing && this.seeker) { + this.startPingMechanic(); + } + + this.room.sendChat( + "Final Hide! The seeker is getting desperate..." + ); + + // Set timer for end of final hide + const finalHideMs = this.room.settings.finalHideTime * 1000; + this._finalHideCheckTimer = setTimeout(() => { + // Hiders win if they survive the final hide + this.endGame("hiders", "All hiders survived the final hide"); + }, finalHideMs); + } + + /** + * Start the ping mechanic for final hide. + * Periodically reveals hider positions to the seeker. + */ + private startPingMechanic() { + if (!this.seeker || this.phase === HideAndSeekPhase.Ended) return; + + const pingInterval = Math.min(this.room.settings.maxPingTime * 1000, 5000); + + const pingTimer = setInterval(() => { + if (this.phase === HideAndSeekPhase.Ended || !this.seeker) { + clearInterval(pingTimer); + return; + } + + // Broadcast a map-wide ping showing hider locations + // This would send position data to the seeker + // The exact protocol for this depends on the game version + this.logger.debug("Ping: revealing hider positions to seeker"); + }, pingInterval); + } + + /** + * Handle a murder RPC from the seeker. + */ + async handleMurder(killer: Player, target: Player) { + // Only the seeker can kill + if (killer.clientId !== this.seeker?.clientId) { + this.logger.warn("Non-seeker %s attempted to kill in Hide and Seek", killer); + return false; + } + + // Can only kill during Seeking or FinalHide phases + if (this.phase !== HideAndSeekPhase.Seeking && this.phase !== HideAndSeekPhase.FinalHide) { + this.logger.warn("Seeker attempted to kill during non-seeking phase: %s", HideAndSeekPhase[this.phase]); + return false; + } + + // Kill the target via character control + // In Hide and Seek, the murder RPC is processed normally by the game engine + // The target's death is handled by the PlayerControl component + + // Remove from hiders list + this.hiders = this.hiders.filter(h => h.clientId !== target.clientId); + + this.logger.info("Seeker %s caught hider %s! (%s remaining)", + killer, target, this.hiders.length); + + // Emit hider caught event + await this.room.emit(new RoomHiderCaughtEvent( + this.room, + target, + killer, + this.hiders.length + )); + + // No body report in Hide and Seek — game continues immediately + + // Check win condition: all hiders dead + if (this.hiders.length === 0) { + await this.endGame("seekers", "All hiders were caught"); + } + + return true; + } + + /** + * Handle task completion from a hider. + */ + async handleTaskComplete(player: Player, taskType: number, taskId: number) { + if (this.phase === HideAndSeekPhase.Ended) return; + + const taskKey = `${player.clientId}:${taskId}`; + this._completedTasks.add(taskKey); + + this.logger.debug("Hider %s completed task %s (type %s). %s/%s total tasks done", + player, taskId, taskType, + this._completedTasks.size, this._totalTasks); + + // Check win condition: all tasks completed + if (this._completedTasks.size >= this._totalTasks) { + await this.endGame("hiders", "All tasks completed"); + } + } + + /** + * Handle report dead body RPC. + * In Hide and Seek, body reports are disabled — meetings never happen. + */ + handleReportBody(reporter: Player, bodyPlayerId: number): boolean { + // Body reports are disabled in Hide and Seek + this.logger.debug("Body report blocked in Hide and Seek mode (reporter: %s, body: %s)", + reporter, bodyPlayerId); + return false; + } + + /** + * End the Hide and Seek game. + */ + async endGame(winner: "seekers" | "hiders" | "none", reason: string) { + if (this.phase === HideAndSeekPhase.Ended) return; + + this.phase = HideAndSeekPhase.Ended; + + // Clear all timers + this.clearTimers(); + + this.logger.info("Hide and Seek ended: %s win — %s", winner, reason); + + // Emit event + await this.room.emit(new RoomHideAndSeekEndEvent( + this.room, + winner, + reason + )); + + // Send result chat + const winnerText = winner === "seekers" + ? "The Seeker wins!" + : winner === "hiders" + ? "The Hiders win!" + : "The game ended!"; + + this.room.sendChat(`${winnerText} ${reason}`); + + // Determine game over reason + const gameOverReason = winner === "seekers" + ? GameOverReason.ImpostorByKill + : winner === "hiders" + ? GameOverReason.CrewmatesByTask + : GameOverReason.CrewmatesByVote; + + await this.room.handleEndGame(gameOverReason); + } + + /** + * Handle a player disconnecting during the game. + */ + async handlePlayerDisconnect(player: Player) { + // If seeker disconnects, hiders win + if (player.clientId === this.seeker?.clientId) { + this.logger.info("Seeker disconnected, hiders win"); + await this.endGame("hiders", "Seeker disconnected"); + return; + } + + // Remove from hiders list + this.hiders = this.hiders.filter(h => h.clientId !== player.clientId); + + // Recalculate task total + this._totalTasks = this.computeTotalTasks(); + + // Check if all hiders are gone + if (this.hiders.length === 0) { + await this.endGame("seekers", "All hiders disconnected"); + } + } + + /** + * Handle RPC messages from players during the game. + * Returns true if the RPC was handled, false to pass through. + */ + async handleRpc(player: Player, rpcMessage: RpcMessage): Promise { + switch (rpcMessage.child?.messageTag) { + case RpcMessageTag.ReportDeadBody: + // Body reports are disabled + return this.handleReportBody(player, (rpcMessage.child as any).bodyPlayerId ?? 0); + + case RpcMessageTag.CompleteTask: + if (rpcMessage.child instanceof CompleteTaskMessage) { + await this.handleTaskComplete( + player, + 0, // taskType derived from taskIdx + rpcMessage.child.taskIdx + ); + } + return true; // Still allow normal processing + + case RpcMessageTag.MurderPlayer: + if (rpcMessage.child instanceof MurderPlayerMessage) { + const target = this.room.players.get(rpcMessage.child.victimNetId); + if (target) { + return await this.handleMurder(player, target); + } + } + return true; + + case RpcMessageTag.StartMeeting: + // Meetings are disabled in Hide and Seek + this.logger.debug("Meeting start blocked in Hide and Seek mode"); + return false; + + default: + return true; // Allow other RPCs through + } + } + + /** + * Calculate total number of tasks to be completed for hider win condition. + */ + private computeTotalTasks(): number { + const settings = this.room.settings; + + // Total tasks = common + long + short, per player + const tasksPerPlayer = (settings.commonTasks || 0) + + (settings.longTasks || 0) + + (settings.shortTasks || 0); + + return tasksPerPlayer * this.hiders.length; + } + + /** + * Clean up all timers. + */ + private clearTimers() { + if (this._hideTimer) { + clearTimeout(this._hideTimer); + this._hideTimer = null; + } + if (this._finalHideTimer) { + clearTimeout(this._finalHideTimer); + this._finalHideTimer = null; + } + if (this._finalHideCheckTimer) { + clearTimeout(this._finalHideCheckTimer); + this._finalHideCheckTimer = null; + } + } + + /** + * Clean up the manager. Call when the room is destroyed. + */ + destroy() { + this.clearTimers(); + this.seeker = null; + this.hiders = []; + this._completedTasks.clear(); + this.phase = HideAndSeekPhase.Ended; + } +} diff --git a/src/game/modes/index.ts b/src/game/modes/index.ts new file mode 100644 index 000000000..cf6416a3f --- /dev/null +++ b/src/game/modes/index.ts @@ -0,0 +1 @@ +export * from "./HideAndSeekManager"; diff --git a/src/game/roles/BaseRole.ts b/src/game/roles/BaseRole.ts new file mode 100644 index 000000000..da37e64be --- /dev/null +++ b/src/game/roles/BaseRole.ts @@ -0,0 +1,120 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; + +/** + * Base class for all role implementations in Waterway. + * + * Each role overrides specific hooks to implement its unique behavior. + * Roles are assigned to players when a game starts based on role settings. + */ +export abstract class BaseRole { + /** The unique role type identifier from the Among Us protocol. */ + abstract roleType: RoleType; + + /** Which team this role belongs to (Crewmate or Impostor). */ + abstract teamType: RoleTeamType; + + /** Whether this role's ability is currently active. */ + isActive: boolean = false; + + /** The current cooldown timer for the role's ability (in milliseconds). */ + currentCooldown: number = 0; + + /** When the ability was last used (Unix timestamp in ms). */ + lastAbilityUse: number = 0; + + /** Whether this role's ability is on cooldown. */ + get isOnCooldown(): boolean { + return this.currentCooldown > 0 && (Date.now() - this.lastAbilityUse) < this.currentCooldown; + } + + constructor( + /** The room this role exists in. */ + public readonly room: Room, + /** The player who has this role. */ + public readonly player: Player, + ) {} + + /** + * Called once when the role is assigned and the game starts. + * Use this to initialize role state, set up timers, etc. + */ + onGameStart(): void {} + + /** + * Called when the player with this role kills another player. + * Override in imposter roles. + * + * @param target The player being killed. + * @returns Whether the kill should proceed normally (true) or be handled by the role (false). + */ + onKill(target: Player): boolean { + return true; // Allow normal kill behavior + } + + /** + * Called when the player completes a task. + * + * @param taskType The type of task completed. + * @param taskId The unique task ID. + */ + onTaskComplete(taskType: number, taskId: number): void {} + + /** + * Called when a meeting starts. + */ + onMeetingStart(): void {} + + /** + * Called when the player dies. + * Return false to prevent the normal death behavior (e.g., Phantom). + */ + onDeath(): boolean { + return true; // Allow normal death behavior + } + + /** + * Called when the player uses their role ability. + * Subclasses override this to implement the ability. + * + * @param target Optional target player for targeted abilities. + * @returns Whether the ability was successfully used. + */ + onAbilityUse(target?: Player): boolean { + return false; + } + + /** + * Called every fixed update tick while the game is running. + * Use for cooldown management, periodic effects, etc. + */ + onFixedUpdate(): void {} + + /** + * Called when the game ends. + */ + onGameEnd(): void {} + + /** + * Start the cooldown for this role's ability. + * @param cooldownMs Cooldown duration in milliseconds. + */ + protected startCooldown(cooldownMs: number): void { + this.currentCooldown = cooldownMs; + this.lastAbilityUse = Date.now(); + } + + /** + * Check if the ability can be used (not on cooldown and role is active). + */ + canUseAbility(): boolean { + return this.isActive && !this.isOnCooldown; + } + + /** + * Get a human-readable name for this role type. + */ + getRoleName(): string { + return RoleType[this.roleType] || "Unknown"; + } +} diff --git a/src/game/roles/DetectiveRole.ts b/src/game/roles/DetectiveRole.ts new file mode 100644 index 000000000..9d74168f8 --- /dev/null +++ b/src/game/roles/DetectiveRole.ts @@ -0,0 +1,110 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; + +/** + * Detective Role + * + * Can inspect a player to see their "suspicion" level — whether they have + * killed someone or performed other suspicious actions. + * + * This is a Crewmate role. + */ +export class DetectiveRole extends BaseRole { + roleType = RoleType.Detective; + teamType = RoleTeamType.Crewmate; + + /** How many suspects have been inspected this game. */ + inspectedCount: number = 0; + + /** Set of player IDs that have been inspected. */ + inspectedPlayers: Set = new Set(); + + /** Records of player kills tracked by the detective. */ + private _murderRecords: Map = new Map(); + + get suspectLimit(): number { + return this.room.settings.roleSettings.detectiveSuspectLimit || 3; + } + + onGameStart(): void { + this.isActive = true; + this.inspectedCount = 0; + this.inspectedPlayers.clear(); + this._murderRecords.clear(); + + this.room.logger.info("%s is the Detective (suspect limit: %s)", + this.player, this.suspectLimit); + + // Listen for murders to track who kills + this.room.on("player.murder", (ev: any) => { + if (ev.player && ev.target) { + const killerId = ev.player.clientId; + const currentCount = this._murderRecords.get(killerId) || 0; + this._murderRecords.set(killerId, currentCount + 1); + } + }); + } + + /** + * Inspect a player to check their suspicion level. + */ + onAbilityUse(target?: Player): boolean { + if (!target) { + this.room.logger.warn("%s (Detective) attempted to inspect but no target specified", this.player); + return false; + } + + if (this.inspectedPlayers.has(target.clientId)) { + this.room.logger.warn("%s (Detective) already inspected %s", this.player, target); + return false; + } + + if (this.inspectedCount >= this.suspectLimit) { + this.room.logger.warn("%s (Detective) has reached the suspect limit (%s)", + this.player, this.suspectLimit); + return false; + } + + // Perform the inspection + this.inspectedCount++; + this.inspectedPlayers.add(target.clientId); + + const kills = this._murderRecords.get(target.clientId) || 0; + const targetInfo = target.getPlayerInfo(); + const isImpostor = targetInfo?.isImpostor || false; + + this.room.logger.info("%s (Detective) inspected %s: %s kills, isImpostor=%s", + this.player, target, kills, isImpostor); + + // Send the inspection result to the detective + let resultMessage: string; + if (kills > 0) { + resultMessage = `Detective report: ${target.username || "Player"} has committed ${kills} murder(s)! Highly suspicious!`; + } else if (isImpostor) { + resultMessage = `Detective report: ${target.username || "Player"} appears suspicious but no kills detected.`; + } else { + resultMessage = `Detective report: ${target.username || "Player"} appears innocent.`; + } + + this.room.sendChat(resultMessage, { targets: [this.player] }); + + return true; + } + + /** + * Get the number of remaining inspections available. + */ + get remainingInspections(): number { + return Math.max(0, this.suspectLimit - this.inspectedCount); + } + + onDeath(): boolean { + this.isActive = false; + return true; + } + + onGameEnd(): void { + this.isActive = false; + } +} diff --git a/src/game/roles/NoisemakerRole.ts b/src/game/roles/NoisemakerRole.ts new file mode 100644 index 000000000..568252552 --- /dev/null +++ b/src/game/roles/NoisemakerRole.ts @@ -0,0 +1,84 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; + +/** + * Noisemaker Role + * + * When this player completes a task, they emit a noise alert that reveals + * their position to nearby players (and optionally to impostors). + * + * This is a Crewmate role. + */ +export class NoisemakerRole extends BaseRole { + roleType = RoleType.Noisemaker; + teamType = RoleTeamType.Crewmate; + + /** Duration of the alert in seconds. */ + get alertDuration(): number { + return this.room.settings.roleSettings.noisemakerAlertDuration || 10; + } + + /** Whether impostors also see the alert. */ + get impostorAlert(): boolean { + return this.room.settings.roleSettings.noisemakerImpostorAlert !== false; + } + + onGameStart(): void { + this.isActive = true; + this.room.logger.info("%s is the Noisemaker (alert duration: %ss, impostor alert: %s)", + this.player, this.alertDuration, this.impostorAlert); + } + + onTaskComplete(taskType: number, taskId: number): void { + if (!this.isActive) return; + + this.room.logger.info("%s (Noisemaker) completed task %s, triggering alert!", + this.player, taskId); + + // Broadcast the alert to nearby players + this.triggerAlert(); + } + + /** + * Trigger the noise alert, revealing this player's position. + */ + private triggerAlert(): void { + const characterControl = this.player.characterControl; + if (!characterControl) return; + + // The alert is broadcast to players based on proximity + // In the Among Us protocol, this is typically done by sending + // a position snap/sync to relevant players + + // Determine who should see the alert + const recipients: Player[] = []; + + for (const [, otherPlayer] of this.room.players) { + if (otherPlayer.clientId === this.player.clientId) continue; + + const otherInfo = otherPlayer.getPlayerInfo(); + if (!otherInfo) continue; + + // Impostors can see if configured, crewmates always see + if (otherInfo.isImpostor && !this.impostorAlert) continue; + + recipients.push(otherPlayer); + } + + // Send a chat message to indicate the alert + this.room.sendChat( + `🔔 Noisemaker alert! ${this.player.username || "Someone"} revealed their position!` + ); + + this.room.logger.debug("Noisemaker alert sent to %s players", recipients.length); + + // Start cooldown after alert + this.startCooldown(this.alertDuration * 1000); + } + + onDeath(): boolean { + this.isActive = false; + return true; + } +} diff --git a/src/game/roles/PhantomRole.ts b/src/game/roles/PhantomRole.ts new file mode 100644 index 000000000..4f547af06 --- /dev/null +++ b/src/game/roles/PhantomRole.ts @@ -0,0 +1,110 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; + +/** + * Phantom Role + * + * When this player is killed, instead of dying immediately, they enter a + * "vanished" ghost state where they can continue moving invisibly for a + * limited duration. After the duration expires, they properly die. + * + * This is an Impostor role. + */ +export class PhantomRole extends BaseRole { + roleType = RoleType.Phantom; + teamType = RoleTeamType.Impostor; + + /** Whether the phantom is currently in the vanished state. */ + isVanished: boolean = false; + + /** Timer for the vanish duration. */ + private _vanishTimer: NodeJS.Timeout | null = null; + + /** Timer for the cooldown between vanish uses. */ + private _cooldownTimer: NodeJS.Timeout | null = null; + + get cooldown(): number { + return this.room.settings.roleSettings.phantomCooldown || 15; + } + + get duration(): number { + return this.room.settings.roleSettings.phantomDuration || 30; + } + + onGameStart(): void { + this.isActive = true; + this.room.logger.info("%s is the Phantom (duration: %ss, cooldown: %ss)", + this.player, this.duration, this.cooldown); + } + + /** + * The Phantom doesn't die immediately. Instead, they enter a vanished state. + */ + onDeath(): boolean { + if (!this.isActive) return true; // Normal death + + // Enter vanished state instead of dying + this.isVanished = true; + this.room.logger.info("%s (Phantom) entered vanished state for %ss", + this.player, this.duration); + + // Notify the player + this.room.sendChat( + `${this.player.username || "Someone"} has vanished into the shadows...`, + { targets: [...this.room.players.values()] } + ); + + // Set a timer to properly die after the duration + this._vanishTimer = setTimeout(() => { + this.endVanish(); + }, this.duration * 1000); + + // Disable the ability until cooldown + this.isActive = false; + this._cooldownTimer = setTimeout(() => { + this.isActive = true; + this.room.logger.debug("%s Phantom ability ready again", this.player); + }, this.cooldown * 1000); + + return false; // Prevent normal death + } + + /** + * End the vanished state and properly kill the phantom. + */ + private endVanish(): void { + if (!this.isVanished) return; + + this.isVanished = false; + this.room.logger.info("%s (Phantom) vanished state ended", this.player); + + // Clear timer + if (this._vanishTimer) { + clearTimeout(this._vanishTimer); + this._vanishTimer = null; + } + + // The player actually dies now via the normal game engine mechanics + } + + /** + * Force-end the vanish state (e.g., game ends). + */ + forceEndVanish(): void { + if (this._vanishTimer) { + clearTimeout(this._vanishTimer); + this._vanishTimer = null; + } + this.isVanished = false; + } + + onGameEnd(): void { + this.forceEndVanish(); + if (this._cooldownTimer) { + clearTimeout(this._cooldownTimer); + this._cooldownTimer = null; + } + this.isActive = false; + } +} diff --git a/src/game/roles/RoleManager.ts b/src/game/roles/RoleManager.ts new file mode 100644 index 000000000..caf71927e --- /dev/null +++ b/src/game/roles/RoleManager.ts @@ -0,0 +1,242 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; +import { NoisemakerRole } from "./NoisemakerRole"; +import { PhantomRole } from "./PhantomRole"; +import { TrackerRole } from "./TrackerRole"; +import { DetectiveRole } from "./DetectiveRole"; +import { ViperRole } from "./ViperRole"; + +/** + * Maps role types to their implementing classes. + */ +const ROLE_CONSTRUCTORS: Partial) => BaseRole>> = { + [RoleType.Noisemaker]: NoisemakerRole, + [RoleType.Phantom]: PhantomRole, + [RoleType.Tracker]: TrackerRole, + [RoleType.Detective]: DetectiveRole, + [RoleType.Viper]: ViperRole, +}; + +/** + * Manages role assignment and lifecycle for a room. + * + * When a game starts, the RoleManager reads the room's role settings + * and assigns roles to players based on the configured chances and + * maximum player counts. + */ +export class RoleManager { + /** All active role instances, keyed by player client ID. */ + activeRoles: Map = new Map(); + + constructor(public readonly room: Room) {} + + /** + * Assign roles to players based on the room's role settings. + * Called when the game starts. + */ + assignRoles(): void { + const settings = this.room.settings; + const roleChances = settings.roleSettings.roleChances; + + if (!roleChances) return; + + const availablePlayers = [...this.room.players.values()].filter( + p => p.characterControl && p.inScene + ); + + // Track how many of each role we've assigned + const assignedCounts: Partial> = {}; + + for (const [roleTypeStr, roleChance] of Object.entries(roleChances)) { + const roleType = parseInt(roleTypeStr) as RoleType; + + // Skip if no chance or no constructor for this role + if (roleChance.chance <= 0) continue; + if (!ROLE_CONSTRUCTORS[roleType]) continue; + + const RoleCtor = ROLE_CONSTRUCTORS[roleType]!; + assignedCounts[roleType] = 0; + + for (const player of availablePlayers) { + // Check max players limit for this role + if (assignedCounts[roleType]! >= roleChance.maxPlayers) break; + + // Skip players that already have a role + if (this.activeRoles.has(player.clientId)) continue; + + // Skip players who are already the standard impostor (for crewmate roles) + const playerInfo = player.getPlayerInfo(); + const isImpostor = playerInfo?.isImpostor || false; + const isCrewmateRole = [ + RoleType.Scientist, + RoleType.Engineer, + RoleType.GuardianAngel, + RoleType.Noisemaker, + RoleType.Tracker, + RoleType.Detective, + ].includes(roleType); + + const isImpostorRole = [ + RoleType.Shapeshifter, + RoleType.Phantom, + RoleType.Viper, + ].includes(roleType); + + // Crewmate roles should not be assigned to impostors + if (isCrewmateRole && isImpostor) continue; + // Impostor roles should not be assigned to crewmates + if (isImpostorRole && !isImpostor) continue; + + // Roll the dice + const roll = Math.random() * 100; + if (roll <= roleChance.chance) { + this.assignRoleToPlayer(player, RoleCtor, roleType); + assignedCounts[roleType]!++; + } + } + } + + // Log assignment summary + const assignedCount = this.activeRoles.size; + if (assignedCount > 0) { + this.room.logger.info("Assigned %s role(s) to players", assignedCount); + for (const [playerId, role] of this.activeRoles) { + const player = this.room.players.get(playerId); + this.room.logger.info(" %s → %s", player, role.getRoleName()); + } + } + } + + /** + * Create and assign a role to a specific player. + */ + assignRoleToPlayer( + player: Player, + RoleCtor: new (room: Room, player: Player) => BaseRole, + roleType: RoleType + ): BaseRole { + const role = new RoleCtor(this.room, player); + this.activeRoles.set(player.clientId, role); + + // The player's role is tracked by this manager + // The client-facing role is set via PlayerControl.setRole RPC + if (player.characterControl) { + (player.characterControl as any).setRole(roleType); + } + + // Initialize the role + role.onGameStart(); + + return role; + } + + /** + * Get the role instance for a player, if any. + */ + getRoleForPlayer(player: Player): BaseRole | null { + return this.activeRoles.get(player.clientId) || null; + } + + /** + * Check if a player has a specific role type. + */ + playerHasRole(player: Player, roleType: RoleType): boolean { + const role = this.activeRoles.get(player.clientId); + return role?.roleType === roleType; + } + + /** + * Called when a player with a role kills someone. + * Routes to the appropriate role's onKill handler. + */ + handleKill(killer: Player, target: Player): boolean { + const role = this.activeRoles.get(killer.clientId); + if (!role) return true; // No special role, allow normal kill + + return role.onKill(target); + } + + /** + * Called when a player with a role completes a task. + * Routes to the appropriate role's onTaskComplete handler. + */ + handleTaskComplete(player: Player, taskType: number, taskId: number): void { + const role = this.activeRoles.get(player.clientId); + if (!role) return; + + role.onTaskComplete(taskType, taskId); + } + + /** + * Called when a player with a role dies. + * Routes to the appropriate role's onDeath handler. + */ + handleDeath(player: Player): boolean { + const role = this.activeRoles.get(player.clientId); + if (!role) return true; // No special role, allow normal death + + const result = role.onDeath(); + + // If the role allowed normal death, clean up + if (result) { + this.activeRoles.delete(player.clientId); + } + + return result; + } + + /** + * Called when a meeting starts. + */ + handleMeetingStart(): void { + for (const [, role] of this.activeRoles) { + role.onMeetingStart(); + } + } + + /** + * Called every fixed update tick. + */ + handleFixedUpdate(): void { + for (const [, role] of this.activeRoles) { + role.onFixedUpdate(); + } + } + + /** + * Called when the game ends. + * Cleans up all active roles. + */ + handleGameEnd(): void { + for (const [, role] of this.activeRoles) { + role.onGameEnd(); + } + this.activeRoles.clear(); + } + + /** + * Called when a player uses their role ability. + */ + handleAbilityUse(player: Player, target?: Player): boolean { + const role = this.activeRoles.get(player.clientId); + if (!role) return false; + + return role.onAbilityUse(target); + } + + /** + * Get all active role instances. + */ + getAllRoles(): BaseRole[] { + return [...this.activeRoles.values()]; + } + + /** + * Register a custom role constructor. + * Allows plugins to add new roles. + */ + static registerRole(roleType: RoleType, ctor: new (room: Room, player: Player) => BaseRole): void { + ROLE_CONSTRUCTORS[roleType] = ctor; + } +} diff --git a/src/game/roles/TrackerRole.ts b/src/game/roles/TrackerRole.ts new file mode 100644 index 000000000..79c8af96e --- /dev/null +++ b/src/game/roles/TrackerRole.ts @@ -0,0 +1,132 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; + +/** + * Tracker Role + * + * Can mark a target player and receive periodic position updates + * (arrow indicators pointing to the target's direction). + * + * This is a Crewmate role. + */ +export class TrackerRole extends BaseRole { + roleType = RoleType.Tracker; + teamType = RoleTeamType.Crewmate; + + /** The currently tracked player, if any. */ + trackedTarget: Player | null = null; + + /** Timer for periodic position updates. */ + private _trackingTimer: NodeJS.Timeout | null = null; + + /** How long the tracking has been active. */ + private _trackingElapsed: number = 0; + + get cooldown(): number { + return this.room.settings.roleSettings.trackerCooldown || 15; + } + + get duration(): number { + return this.room.settings.roleSettings.trackerDuration || 30; + } + + get delay(): number { + return this.room.settings.roleSettings.trackerDelay || 1; + } + + onGameStart(): void { + this.isActive = true; + this.room.logger.info("%s is the Tracker (duration: %ss, cooldown: %ss, delay: %ss)", + this.player, this.duration, this.cooldown, this.delay); + } + + /** + * Use the tracking ability on a target player. + */ + onAbilityUse(target?: Player): boolean { + if (!target) { + this.room.logger.warn("%s (Tracker) attempted to track but no target specified", this.player); + return false; + } + + if (!this.canUseAbility()) { + this.room.logger.warn("%s (Tracker) ability on cooldown", this.player); + return false; + } + + if (target.clientId === this.player.clientId) { + this.room.logger.warn("%s (Tracker) attempted to track themselves", this.player); + return false; + } + + // Start tracking + this.trackedTarget = target; + this._trackingElapsed = 0; + this.startCooldown(this.cooldown * 1000); + + this.room.logger.info("%s (Tracker) is now tracking %s", this.player, target); + + this.room.sendChat( + `Tracker is on the hunt...`, + { targets: [this.player] } + ); + + // Start periodic position updates + this._trackingTimer = setInterval(() => { + this.sendPositionUpdate(); + this._trackingElapsed += this.delay; + + // Stop tracking after duration expires + if (this._trackingElapsed >= this.duration) { + this.stopTracking(); + } + }, this.delay * 1000); + + return true; + } + + /** + * Send a position update about the tracked target to this player. + */ + private sendPositionUpdate(): void { + if (!this.trackedTarget || !this.trackedTarget.characterControl) { + this.stopTracking(); + return; + } + + // In the actual game, this would send an arrow/indicator RPC + // For now, we log the position update + this.room.logger.debug("%s (Tracker) received position update for %s", + this.player, this.trackedTarget); + } + + /** + * Stop tracking the current target. + */ + private stopTracking(): void { + if (this._trackingTimer) { + clearInterval(this._trackingTimer); + this._trackingTimer = null; + } + + if (this.trackedTarget) { + this.room.logger.info("%s (Tracker) stopped tracking %s", + this.player, this.trackedTarget); + } + + this.trackedTarget = null; + this._trackingElapsed = 0; + } + + onDeath(): boolean { + this.stopTracking(); + this.isActive = false; + return true; + } + + onGameEnd(): void { + this.stopTracking(); + this.isActive = false; + } +} diff --git a/src/game/roles/ViperRole.ts b/src/game/roles/ViperRole.ts new file mode 100644 index 000000000..eaf0ddfd5 --- /dev/null +++ b/src/game/roles/ViperRole.ts @@ -0,0 +1,134 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; + +/** + * Viper Role + * + * When this impostor kills a player, the target doesn't die immediately. + * Instead, they become "poisoned" and die after a dissolve delay. + * This creates confusion as the kill is delayed and may happen during + * a meeting or while the viper is elsewhere (providing an alibi). + * + * This is an Impostor role. + */ +export class ViperRole extends BaseRole { + roleType = RoleType.Viper; + teamType = RoleTeamType.Impostor; + + /** Set of poisoned player IDs awaiting dissolve. */ + private _poisonedPlayers: Set = new Set(); + + /** Timers for each poisoned player. */ + private _poisonTimers: Map = new Map(); + + get dissolveTime(): number { + return this.room.settings.roleSettings.viperDissolveTime || 15; + } + + onGameStart(): void { + this.isActive = true; + this._poisonedPlayers.clear(); + this._poisonTimers.clear(); + + this.room.logger.info("%s is the Viper (dissolve time: %ss)", + this.player, this.dissolveTime); + } + + /** + * When the Viper kills, the target is poisoned instead of dying immediately. + */ + onKill(target: Player): boolean { + if (!this.isActive) return true; // Normal kill if ability inactive + + this.room.logger.info("%s (Viper) poisoned %s (dissolve in %ss)", + this.player, target, this.dissolveTime); + + // Mark as poisoned + this._poisonedPlayers.add(target.clientId); + + // Don't kill immediately — the dissolve timer will handle death + // But we still need to prevent the normal kill from processing + // The actual kill happens after the dissolve time + + // Send a subtle message to the target + this.room.sendChat( + `You feel a sharp sting... something is wrong...`, + { targets: [target] } + ); + + // Set the dissolve timer + const timer = setTimeout(() => { + this.dissolveTarget(target); + }, this.dissolveTime * 1000); + + this._poisonTimers.set(target.clientId, timer); + + return false; // Prevent normal kill — Viper handles it + } + + /** + * Actually kill the poisoned target after the dissolve time. + */ + private dissolveTarget(target: Player): void { + if (!this._poisonedPlayers.has(target.clientId)) return; + + this._poisonedPlayers.delete(target.clientId); + this._poisonTimers.delete(target.clientId); + + // Check if the target is already dead + const playerInfo = target.getPlayerInfo(); + if (playerInfo?.isDead) { + this.room.logger.debug("%s (Viper) target %s was already dead, skipping dissolve", + this.player, target); + return; + } + + this.room.logger.info("%s (Viper) poison dissolved on %s", this.player, target); + + // Send death message + this.room.sendChat( + `${target.username || "Someone"} succumbed to poison!` + ); + + // The target dies via the normal game engine mechanics + // Poison mechanic is tracked by this role for timing + } + + /** + * Check if a player is currently poisoned by this Viper. + */ + isPoisoned(playerId: number): boolean { + return this._poisonedPlayers.has(playerId); + } + + /** + * Get all currently poisoned player IDs. + */ + getPoisonedPlayers(): number[] { + return [...this._poisonedPlayers]; + } + + onDeath(): boolean { + // Clear all poison when the Viper dies + this.clearAllPoisons(); + this.isActive = false; + return true; + } + + onGameEnd(): void { + this.clearAllPoisons(); + this.isActive = false; + } + + /** + * Clear all active poisons (e.g., game over, Viper dies). + */ + private clearAllPoisons(): void { + for (const [, timer] of this._poisonTimers) { + clearTimeout(timer); + } + this._poisonTimers.clear(); + this._poisonedPlayers.clear(); + } +} diff --git a/src/game/roles/index.ts b/src/game/roles/index.ts new file mode 100644 index 000000000..bc16b4456 --- /dev/null +++ b/src/game/roles/index.ts @@ -0,0 +1,7 @@ +export * from "./BaseRole"; +export * from "./DetectiveRole"; +export * from "./NoisemakerRole"; +export * from "./PhantomRole"; +export * from "./RoleManager"; +export * from "./TrackerRole"; +export * from "./ViperRole"; diff --git a/yarn.lock b/yarn.lock index eb26087cd..dc8d30e29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,2984 +1,1619 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10 - -"@cspotcode/source-map-support@npm:^0.8.0": - version: 0.8.1 - resolution: "@cspotcode/source-map-support@npm:0.8.1" - dependencies: - "@jridgewell/trace-mapping": "npm:0.3.9" - checksum: 10/b6e38a1712fab242c86a241c229cf562195aad985d0564bd352ac404be583029e89e93028ffd2c251d2c407ecac5fb0cbdca94a2d5c10f29ac806ede0508b3ff - languageName: node - linkType: hard - -"@gerrit0/mini-shiki@npm:^3.12.0": - version: 3.13.1 - resolution: "@gerrit0/mini-shiki@npm:3.13.1" - dependencies: - "@shikijs/engine-oniguruma": "npm:^3.13.0" - "@shikijs/langs": "npm:^3.13.0" - "@shikijs/themes": "npm:^3.13.0" - "@shikijs/types": "npm:^3.13.0" - "@shikijs/vscode-textmate": "npm:^10.0.2" - checksum: 10/e7606dae2846cd69defb5443af38a9edd70eea241753459ffdd237a082f70200096bd501bc988fb0b49485924f2a61fed2374f89be8ef5d09f34859ba938db0b - languageName: node - linkType: hard - -"@hapi/bourne@npm:^3.0.0": - version: 3.0.0 - resolution: "@hapi/bourne@npm:3.0.0" - checksum: 10/b3b5d7bdf511fe27b7b8b01b9457f125646665bef72a78848c69170efdea19c2b72522246a87ede6cd811e51e7a556ceff194e46fb1393c6c8c796431c1810b6 - languageName: node - linkType: hard - -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" - dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10/e9ed5fd27c3aec1095e3a16e0c0cf148d1fee55a38665c35f7b3f86a9b5d00d042ddaabc98e8a1cb7463b9378c15f22a94eb35e99469c201453eb8375191f243 - languageName: node - linkType: hard - -"@isaacs/fs-minipass@npm:^4.0.0": - version: 4.0.1 - resolution: "@isaacs/fs-minipass@npm:4.0.1" - dependencies: - minipass: "npm:^7.0.4" - checksum: 10/4412e9e6713c89c1e66d80bb0bb5a2a93192f10477623a27d08f228ba0316bb880affabc5bfe7f838f58a34d26c2c190da726e576cdfc18c49a72e89adabdcf5 - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:^3.0.3": - version: 3.1.2 - resolution: "@jridgewell/resolve-uri@npm:3.1.2" - checksum: 10/97106439d750a409c22c8bff822d648f6a71f3aa9bc8e5129efdc36343cd3096ddc4eeb1c62d2fe48e9bdd4db37b05d4646a17114ecebd3bbcacfa2de51c3c1d - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.10": - version: 1.5.5 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" - checksum: 10/5d9d207b462c11e322d71911e55e21a4e2772f71ffe8d6f1221b8eb5ae6774458c1d242f897fb0814e8714ca9a6b498abfa74dfe4f434493342902b1a48b33a5 - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:0.3.9": - version: 0.3.9 - resolution: "@jridgewell/trace-mapping@npm:0.3.9" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.0.3" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - checksum: 10/83deafb8e7a5ca98993c2c6eeaa93c270f6f647a4c0dc00deb38c9cf9b2d3b7bf15e8839540155247ef034a052c0ec4466f980bf0c9e2ab63b97d16c0cedd3ff - languageName: node - linkType: hard - -"@koa/router@npm:^14.0.0": - version: 14.0.0 - resolution: "@koa/router@npm:14.0.0" - dependencies: - debug: "npm:^4.4.1" - http-errors: "npm:^2.0.0" - koa-compose: "npm:^4.1.0" - path-to-regexp: "npm:^8.2.0" - checksum: 10/f5f9bedd4c163ad376bcf9626ebb13f35febc44c1f81545ee5efaceb67324e3caf476f9d2a966b4590cac41ab9994b1bcb11f050afbdccd6343f27f31758ff68 - languageName: node - linkType: hard - -"@noble/hashes@npm:^1.1.5": - version: 1.8.0 - resolution: "@noble/hashes@npm:1.8.0" - checksum: 10/474b7f56bc6fb2d5b3a42132561e221b0ea4f91e590f4655312ca13667840896b34195e2b53b7f097ec080a1fdd3b58d902c2a8d0fbdf51d2e238b53808a177e - languageName: node - linkType: hard - -"@npmcli/agent@npm:^3.0.0": - version: 3.0.0 - resolution: "@npmcli/agent@npm:3.0.0" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10/775c9a7eb1f88c195dfb3bce70c31d0fe2a12b28b754e25c08a3edb4bc4816bfedb7ac64ef1e730579d078ca19dacf11630e99f8f3c3e0fd7b23caa5fd6d30a6 - languageName: node - linkType: hard - -"@npmcli/fs@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/fs@npm:4.0.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10/405c4490e1ff11cf299775449a3c254a366a4b1ffc79d87159b0ee7d5558ac9f6a2f8c0735fd6ff3873cef014cb1a44a5f9127cb6a1b2dbc408718cca9365b5a - languageName: node - linkType: hard - -"@paralleldrive/cuid2@npm:^2.2.2": - version: 2.2.2 - resolution: "@paralleldrive/cuid2@npm:2.2.2" - dependencies: - "@noble/hashes": "npm:^1.1.5" - checksum: 10/40ee269d6e47b4fed7706a2e4da7c27c3c668ebc969110d6d112277b6b16a67cce0503b53b9943f2c55035a72d225f77ea5541e03396d6429eec9252137a53b7 - languageName: node - linkType: hard - -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff - languageName: node - linkType: hard - -"@schemastore/package@npm:^0.0.6": - version: 0.0.6 - resolution: "@schemastore/package@npm:0.0.6" - checksum: 10/4f65a8ed9409494f012cb41c601e564695d3d6623f98359cc08158212a18e534e4233147bd04776b92378cbcfea6853dd2abcc7615aea02f74c5a0a77d1ae6c1 - languageName: node - linkType: hard - -"@shikijs/engine-oniguruma@npm:^3.13.0": - version: 3.13.0 - resolution: "@shikijs/engine-oniguruma@npm:3.13.0" - dependencies: - "@shikijs/types": "npm:3.13.0" - "@shikijs/vscode-textmate": "npm:^10.0.2" - checksum: 10/bc7f1c69640ccece9a3ac3f3adc9cebde9c24bcd722747cc464c338a32725af2b9087a42646cd0f56fbea98d99414918c558f2ca0beadc13db0a740183e7707f - languageName: node - linkType: hard - -"@shikijs/langs@npm:^3.13.0": - version: 3.13.0 - resolution: "@shikijs/langs@npm:3.13.0" - dependencies: - "@shikijs/types": "npm:3.13.0" - checksum: 10/2a0478246ce61745d9012cf5051c9790ef5afad6c89fe5716be4a81d04d7dfd9ffc90af0bad0be0061111bfc001a3bc0c55ffd4fc0b82bc1c09a4c33d7b593ca - languageName: node - linkType: hard - -"@shikijs/themes@npm:^3.13.0": - version: 3.13.0 - resolution: "@shikijs/themes@npm:3.13.0" - dependencies: - "@shikijs/types": "npm:3.13.0" - checksum: 10/4ab0ecf1ddb35e387cef22e267cdf3ed7548a84f24ad58885d19401f75cc01a5a10152ee0196222e01cead8202f20012615aa0076f798599f7dfc2bb7beaa617 - languageName: node - linkType: hard - -"@shikijs/types@npm:3.13.0, @shikijs/types@npm:^3.13.0": - version: 3.13.0 - resolution: "@shikijs/types@npm:3.13.0" - dependencies: - "@shikijs/vscode-textmate": "npm:^10.0.2" - "@types/hast": "npm:^3.0.4" - checksum: 10/a57cc95847edd84134166de1f11a044e356581834b2dfd75dcd67f2221aef6f20f505839c090a76b7c8e0a79d7d5cee829de50d261f363a633ae9afca29546c4 - languageName: node - linkType: hard - -"@shikijs/vscode-textmate@npm:^10.0.2": - version: 10.0.2 - resolution: "@shikijs/vscode-textmate@npm:10.0.2" - checksum: 10/d924cba8a01cd9ca12f56ba097d628fdb81455abb85884c8d8a5ae85b628a37dd5907e7691019b97107bd6608c866adf91ba04a1c3bba391281c88e386c044ea - languageName: node - linkType: hard - -"@skeldjs/au-client@npm:^3.0.3": - version: 3.0.3 - resolution: "@skeldjs/au-client@npm:3.0.3" - checksum: 10/d83bcbf37568863ba874f19a631fd45db3737383689b33885f7a8261a83f486ee3f39bae7eee61d54f27da3c79037caa9aa5d9caf688f52150908f0ac2d4e110 - languageName: node - linkType: hard - -"@skeldjs/au-constants@npm:3.0.3, @skeldjs/au-constants@npm:^3.0.3": - version: 3.0.3 - resolution: "@skeldjs/au-constants@npm:3.0.3" - checksum: 10/47f6738bbdcc8c752ebd6814a38c67b561b7021f54d31c6b0046c3abe9d05d89325d7a5217c3be19b91c0ad93faca29ef12a99e90a517d5a1904b8d0755b3d0e - languageName: node - linkType: hard - -"@skeldjs/au-core@npm:^3.0.3": - version: 3.0.3 - resolution: "@skeldjs/au-core@npm:3.0.3" - dependencies: - "@skeldjs/au-constants": "npm:3.0.3" - "@skeldjs/au-protocol": "npm:3.0.3" - "@skeldjs/events": "npm:3.0.3" - "@skeldjs/hazel": "npm:3.0.3" - checksum: 10/74a1b68a23a88137f155c8a3be154f1ef17f42d70f4cf4ddd908a4d878db8332efd5d6a9a8841e35995ea1a8f22e40893d5258e0e4e1a18bae0fdec71f08a58e - languageName: node - linkType: hard - -"@skeldjs/au-protocol@npm:3.0.3, @skeldjs/au-protocol@npm:^3.0.3": - version: 3.0.3 - resolution: "@skeldjs/au-protocol@npm:3.0.3" - dependencies: - "@skeldjs/au-constants": "npm:3.0.3" - "@skeldjs/hazel": "npm:3.0.3" - checksum: 10/4c937b8bd7567359e6cd170fc190b6cba5dff39112b245339fb9d7d28ab03625184d51b4c812474e6c58f2157621212011020766987fe8c7eff1c3e0e5ba7e5a - languageName: node - linkType: hard - -"@skeldjs/au-text@npm:^3.0.3": - version: 3.0.3 - resolution: "@skeldjs/au-text@npm:3.0.3" - checksum: 10/5f2a948d8b1ec0be3175f0837cb9995c9e8b23d8fd46968200f979c0191e74045d549cc993bdf5507abf121282955607a1067985e94f9863150809bae0b88103 - languageName: node - linkType: hard - -"@skeldjs/events@npm:3.0.3, @skeldjs/events@npm:^3.0.3": - version: 3.0.3 - resolution: "@skeldjs/events@npm:3.0.3" - checksum: 10/9c6aa038e8ed7432906c3928dd5198f07eda63ace7668e093135c080c88a69280bdceda0c685ba4eaeff2f4cdac934e0e97dd45068ed93c2960087d669316293 - languageName: node - linkType: hard - -"@skeldjs/hazel@npm:3.0.3, @skeldjs/hazel@npm:^3.0.3": - version: 3.0.3 - resolution: "@skeldjs/hazel@npm:3.0.3" - checksum: 10/fe09f5b4649b82be2a41a8b45fa6d1325e8e1726999c6d0426f8853e8e7b2b79275484dd7fdc34afe4a0791c5d4b05b6c3b3ee5e8f913b865431c24215fcfc6b - languageName: node - linkType: hard - -"@skeldjs/waterway@workspace:.": - version: 0.0.0-use.local - resolution: "@skeldjs/waterway@workspace:." - dependencies: - "@koa/router": "npm:^14.0.0" - "@schemastore/package": "npm:^0.0.6" - "@skeldjs/au-client": "npm:^3.0.3" - "@skeldjs/au-constants": "npm:^3.0.3" - "@skeldjs/au-core": "npm:^3.0.3" - "@skeldjs/au-protocol": "npm:^3.0.3" - "@skeldjs/au-text": "npm:^3.0.3" - "@skeldjs/events": "npm:^3.0.3" - "@skeldjs/hazel": "npm:^3.0.3" - "@types/koa": "npm:^2" - "@types/koa__router": "npm:^12" - "@types/minimatch": "npm:^3.0.5" - "@types/node": "npm:^24.7.0" - "@types/prompts": "npm:^2.0.14" - "@types/triple-beam": "npm:^1.3.2" - "@types/vorpal": "npm:^1.12.0" - "@types/yargs": "npm:^17.0.0" - chalk: "npm:^4.1.1" - chokidar: "npm:^3.5.2" - compare-versions: "npm:^3.6.0" - dotenv: "npm:^10.0.0" - koa: "npm:^3.0.3" - koa-body: "npm:^6.0.1" - minimatch: "npm:^3.0.4" - pinst: "npm:^2.1.6" - prompts: "npm:^2.4.2" - query-registry: "npm:^2.5.0" - reflect-metadata: "npm:^0.1.13" - resolve-from: "npm:^5.0.0" - resolve-pkg: "npm:^2.0.0" - triple-beam: "npm:^1.3.0" - ts-node: "npm:^10.0.0" - typedoc: "npm:^0.28.13" - typedoc-plugin-external-module-map: "npm:^2.2.0" - typescript: "npm:^5.9.3" - vorpal: "npm:^1.12.0" - bin: - waterway: ./dist/bin/pkg.js - languageName: unknown - linkType: soft - -"@tsconfig/node10@npm:^1.0.7": - version: 1.0.11 - resolution: "@tsconfig/node10@npm:1.0.11" - checksum: 10/51fe47d55fe1b80ec35e6e5ed30a13665fd3a531945350aa74a14a1e82875fb60b350c2f2a5e72a64831b1b6bc02acb6760c30b3738b54954ec2dea82db7a267 - languageName: node - linkType: hard - -"@tsconfig/node12@npm:^1.0.7": - version: 1.0.11 - resolution: "@tsconfig/node12@npm:1.0.11" - checksum: 10/5ce29a41b13e7897a58b8e2df11269c5395999e588b9a467386f99d1d26f6c77d1af2719e407621412520ea30517d718d5192a32403b8dfcc163bf33e40a338a - languageName: node - linkType: hard - -"@tsconfig/node14@npm:^1.0.0": - version: 1.0.3 - resolution: "@tsconfig/node14@npm:1.0.3" - checksum: 10/19275fe80c4c8d0ad0abed6a96dbf00642e88b220b090418609c4376e1cef81bf16237bf170ad1b341452feddb8115d8dd2e5acdfdea1b27422071163dc9ba9d - languageName: node - linkType: hard - -"@tsconfig/node16@npm:^1.0.2": - version: 1.0.4 - resolution: "@tsconfig/node16@npm:1.0.4" - checksum: 10/202319785901f942a6e1e476b872d421baec20cf09f4b266a1854060efbf78cde16a4d256e8bc949d31e6cd9a90f1e8ef8fb06af96a65e98338a2b6b0de0a0ff - languageName: node - linkType: hard - -"@types/accepts@npm:*": - version: 1.3.7 - resolution: "@types/accepts@npm:1.3.7" - dependencies: - "@types/node": "npm:*" - checksum: 10/7678cf74976e16093aff6e6f9755826faf069ac1e30179276158ce46ea246348ff22ca6bdd46cef08428881337d9ceefbf00bab08a7731646eb9fc9449d6a1e7 - languageName: node - linkType: hard - -"@types/body-parser@npm:*": - version: 1.19.6 - resolution: "@types/body-parser@npm:1.19.6" - dependencies: - "@types/connect": "npm:*" - "@types/node": "npm:*" - checksum: 10/33041e88eae00af2cfa0827e951e5f1751eafab2a8b6fce06cd89ef368a988907996436b1325180edaeddd1c0c7d0d0d4c20a6c9ff294a91e0039a9db9e9b658 - languageName: node - linkType: hard - -"@types/co-body@npm:^6.1.0": - version: 6.1.3 - resolution: "@types/co-body@npm:6.1.3" - dependencies: - "@types/node": "npm:*" - "@types/qs": "npm:*" - checksum: 10/e93fdc177f69ee0535cf401783258e4255f5eb8235c58b5a2a5a8958cf341fadf3d0bf2c75907ed6b7d188ce2c2f2cf9593a71d4eef12900beba54ebbbdd5cc1 - languageName: node - linkType: hard - -"@types/connect@npm:*": - version: 3.4.38 - resolution: "@types/connect@npm:3.4.38" - dependencies: - "@types/node": "npm:*" - checksum: 10/7eb1bc5342a9604facd57598a6c62621e244822442976c443efb84ff745246b10d06e8b309b6e80130026a396f19bf6793b7cecd7380169f369dac3bfc46fb99 - languageName: node - linkType: hard - -"@types/content-disposition@npm:*": - version: 0.5.9 - resolution: "@types/content-disposition@npm:0.5.9" - checksum: 10/d895703c0027ca6c4c0c1ede363909dbb590deec3b206a84b88a49c8b2840bcbd31b4ddeb2e1e6caa1af00801cc79c5b69a5c75a8152f2959810b10fe75a4e1f - languageName: node - linkType: hard - -"@types/cookies@npm:*": - version: 0.9.1 - resolution: "@types/cookies@npm:0.9.1" - dependencies: - "@types/connect": "npm:*" - "@types/express": "npm:*" - "@types/keygrip": "npm:*" - "@types/node": "npm:*" - checksum: 10/512635d55e25fb113412a8f0edc59a00f68ab79646ce19ebd95f8e97815f9ea506b69ec61da12e024565bb03d238fbb7983805c53a6f8d92684c45869a9ac7cf - languageName: node - linkType: hard - -"@types/express-serve-static-core@npm:^5.0.0": - version: 5.1.0 - resolution: "@types/express-serve-static-core@npm:5.1.0" - dependencies: - "@types/node": "npm:*" - "@types/qs": "npm:*" - "@types/range-parser": "npm:*" - "@types/send": "npm:*" - checksum: 10/c0b5b7ebc15b222f51e5705da2b8a5180335bf70927cc83c065784331aa9291984db1bfa4a14f5ba31b538dcb543561d9280046051fa4c9b7256eb971293e735 - languageName: node - linkType: hard - -"@types/express@npm:*": - version: 5.0.3 - resolution: "@types/express@npm:5.0.3" - dependencies: - "@types/body-parser": "npm:*" - "@types/express-serve-static-core": "npm:^5.0.0" - "@types/serve-static": "npm:*" - checksum: 10/bb6f10c14c8e3cce07f79ee172688aa9592852abd7577b663cd0c2054307f172c2b2b36468c918fed0d4ac359b99695807b384b3da6157dfa79acbac2226b59b - languageName: node - linkType: hard - -"@types/formidable@npm:^2.0.5": - version: 2.0.6 - resolution: "@types/formidable@npm:2.0.6" - dependencies: - "@types/node": "npm:*" - checksum: 10/808a9bc11276e3bd44a8b9d20f4d567ef0e452dcff7fa6ce2575b769bab049ccc4225d02ddd80f0f4de34aa2d2e7242e036bc547811667868860e8dc632d8b16 - languageName: node - linkType: hard - -"@types/hast@npm:^3.0.4": - version: 3.0.4 - resolution: "@types/hast@npm:3.0.4" - dependencies: - "@types/unist": "npm:*" - checksum: 10/732920d81bb7605895776841b7658b4d8cc74a43a8fa176017cc0fb0ecc1a4c82a2b75a4fe6b71aa262b649d3fb62858c6789efa3793ea1d40269953af96ecb5 - languageName: node - linkType: hard - -"@types/http-assert@npm:*": - version: 1.5.6 - resolution: "@types/http-assert@npm:1.5.6" - checksum: 10/dfe1010164ba633859d90a50c4c53e69a38a16972061ef614acc1b0bdb7e53a1c923a11b4169a4a7eedc20b2303962d761727a212ae099717327cf4f38293817 - languageName: node - linkType: hard - -"@types/http-errors@npm:*, @types/http-errors@npm:^2": - version: 2.0.5 - resolution: "@types/http-errors@npm:2.0.5" - checksum: 10/a88da669366bc483e8f3b3eb3d34ada5f8d13eeeef851b1204d77e2ba6fc42aba4566d877cca5c095204a3f4349b87fe397e3e21288837bdd945dd514120755b - languageName: node - linkType: hard - -"@types/keygrip@npm:*": - version: 1.0.6 - resolution: "@types/keygrip@npm:1.0.6" - checksum: 10/d157f60bf920492347791d2b26d530d5069ce05796549fbacd4c24d66ffbebbcb0ab67b21e7a1b80a593b9fd4b67dc4843dec04c12bbc2e0fddfb8577a826c41 - languageName: node - linkType: hard - -"@types/koa-compose@npm:*": - version: 3.2.8 - resolution: "@types/koa-compose@npm:3.2.8" - dependencies: - "@types/koa": "npm:*" - checksum: 10/95c32bdee738ac7c10439bbf6342ca3b9f0aafd7e8118739eac7fb0fa703a23cfe4c88f63e13a69a16fbde702e0bcdc62b272aa734325fc8efa7e5625479752e - languageName: node - linkType: hard - -"@types/koa@npm:*": - version: 3.0.0 - resolution: "@types/koa@npm:3.0.0" - dependencies: - "@types/accepts": "npm:*" - "@types/content-disposition": "npm:*" - "@types/cookies": "npm:*" - "@types/http-assert": "npm:*" - "@types/http-errors": "npm:^2" - "@types/keygrip": "npm:*" - "@types/koa-compose": "npm:*" - "@types/node": "npm:*" - checksum: 10/db461810b71afe7b73dc849c0832669d1d838edd15b81b0c07d37d32545620dfb0efc19cc120485f5e06a805a151cd89696eb53a5bfad8e223d7e593080069cb - languageName: node - linkType: hard - -"@types/koa@npm:^2, @types/koa@npm:^2.13.5": - version: 2.15.0 - resolution: "@types/koa@npm:2.15.0" - dependencies: - "@types/accepts": "npm:*" - "@types/content-disposition": "npm:*" - "@types/cookies": "npm:*" - "@types/http-assert": "npm:*" - "@types/http-errors": "npm:*" - "@types/keygrip": "npm:*" - "@types/koa-compose": "npm:*" - "@types/node": "npm:*" - checksum: 10/2be9dff1ef66bf15b037386c188893761a8fb46390a5e1d2a2031d9e1ba4473e40ddfbd625980a504bd804d7148b3e230c18e240503f33eac3b6e5e830645d30 - languageName: node - linkType: hard - -"@types/koa__router@npm:^12": - version: 12.0.4 - resolution: "@types/koa__router@npm:12.0.4" - dependencies: - "@types/koa": "npm:*" - checksum: 10/c01311980bf9a921b77cca5a93cc85522a6d13fe49575e6190fa80407a60237e7351d99a399316dda3119641d498f5d8236b905cd3b4f54fad2c0839ab655dd4 - languageName: node - linkType: hard - -"@types/mime@npm:^1": - version: 1.3.5 - resolution: "@types/mime@npm:1.3.5" - checksum: 10/e29a5f9c4776f5229d84e525b7cd7dd960b51c30a0fb9a028c0821790b82fca9f672dab56561e2acd9e8eed51d431bde52eafdfef30f643586c4162f1aecfc78 - languageName: node - linkType: hard - -"@types/minimatch@npm:^3.0.5": - version: 3.0.5 - resolution: "@types/minimatch@npm:3.0.5" - checksum: 10/c41d136f67231c3131cf1d4ca0b06687f4a322918a3a5adddc87ce90ed9dbd175a3610adee36b106ae68c0b92c637c35e02b58c8a56c424f71d30993ea220b92 - languageName: node - linkType: hard - -"@types/node@npm:*, @types/node@npm:^24.7.0": - version: 24.7.2 - resolution: "@types/node@npm:24.7.2" - dependencies: - undici-types: "npm:~7.14.0" - checksum: 10/62073022f7fc11363683e6ce82332193129c3053e7f973127a83b0daa5d755ecf5806d310f5a7c78375e6c3d309aa15b4ae87c4c1797a9eec091425d36098b60 - languageName: node - linkType: hard - -"@types/node@npm:^20.14.14": - version: 20.19.21 - resolution: "@types/node@npm:20.19.21" - dependencies: - undici-types: "npm:~6.21.0" - checksum: 10/5e460265afe6b08e40a60b7b353bfe03836a9f73f5fae9ef90d5fa746f2173cbb4b20874adb611c711d4246f905d92f409339f4e5268048307306c7ecf5a16a7 - languageName: node - linkType: hard - -"@types/prompts@npm:^2.0.14": - version: 2.4.9 - resolution: "@types/prompts@npm:2.4.9" - dependencies: - "@types/node": "npm:*" - kleur: "npm:^3.0.3" - checksum: 10/69b8372f4c790b45fea16a46ff8d1bcc71b14579481776b67bd6263637118a7ecb1f12e1311506c29fadc81bf618dc64f1a91f903cfd5be67a0455a227b3e462 - languageName: node - linkType: hard - -"@types/qs@npm:*": - version: 6.14.0 - resolution: "@types/qs@npm:6.14.0" - checksum: 10/1909205514d22b3cbc7c2314e2bd8056d5f05dfb21cf4377f0730ee5e338ea19957c41735d5e4806c746176563f50005bbab602d8358432e25d900bdf4970826 - languageName: node - linkType: hard - -"@types/range-parser@npm:*": - version: 1.2.7 - resolution: "@types/range-parser@npm:1.2.7" - checksum: 10/95640233b689dfbd85b8c6ee268812a732cf36d5affead89e806fe30da9a430767af8ef2cd661024fd97e19d61f3dec75af2df5e80ec3bea000019ab7028629a - languageName: node - linkType: hard - -"@types/send@npm:*": - version: 1.2.0 - resolution: "@types/send@npm:1.2.0" - dependencies: - "@types/node": "npm:*" - checksum: 10/11b4a178902deae8743e92a12ce0821200c285a75fade3b44ca5ee66f65997cd9ce86006622f0a1adaef8fa5272ac06bf464fab34089e36dca8e57197ff32544 - languageName: node - linkType: hard - -"@types/send@npm:<1": - version: 0.17.5 - resolution: "@types/send@npm:0.17.5" - dependencies: - "@types/mime": "npm:^1" - "@types/node": "npm:*" - checksum: 10/b68ae8f9ba9328a4f276cd010914ed43b96371fbf34c7aa08a9111bff36661810bb14b96647e4a92e319dbd2689dc107fb0f9194ec3fa9335c162dc134026240 - languageName: node - linkType: hard - -"@types/serve-static@npm:*": - version: 1.15.9 - resolution: "@types/serve-static@npm:1.15.9" - dependencies: - "@types/http-errors": "npm:*" - "@types/node": "npm:*" - "@types/send": "npm:<1" - checksum: 10/5b7a24c1e5fb474ae539165451dae4e3e85e104d9935562de9c5f0c6b2557395aefe6fdbaf094178ea94d58fd9f26e9605a51ccaf101655fcda18971840c06ad - languageName: node - linkType: hard - -"@types/triple-beam@npm:^1.3.2": - version: 1.3.5 - resolution: "@types/triple-beam@npm:1.3.5" - checksum: 10/519b6a1b30d4571965c9706ad5400a200b94e4050feca3e7856e3ea7ac00ec9903e32e9a10e2762d0f7e472d5d03e5f4b29c16c0bd8c1f77c8876c683b2231f1 - languageName: node - linkType: hard - -"@types/unist@npm:*": - version: 3.0.3 - resolution: "@types/unist@npm:3.0.3" - checksum: 10/96e6453da9e075aaef1dc22482463898198acdc1eeb99b465e65e34303e2ec1e3b1ed4469a9118275ec284dc98019f63c3f5d49422f0e4ac707e5ab90fb3b71a - languageName: node - linkType: hard - -"@types/vorpal@npm:^1.12.0": - version: 1.12.8 - resolution: "@types/vorpal@npm:1.12.8" - checksum: 10/69822ba025aa179af3bb0a1b8f18d73ba5db92c499a66e96f241cfe2fb97e8072478ef435a107f946dd4fd6d7d2533d637f9edc9c33256897d5834d5ce27ac67 - languageName: node - linkType: hard - -"@types/yargs-parser@npm:*": - version: 21.0.3 - resolution: "@types/yargs-parser@npm:21.0.3" - checksum: 10/a794eb750e8ebc6273a51b12a0002de41343ffe46befef460bdbb57262d187fdf608bc6615b7b11c462c63c3ceb70abe2564c8dd8ee0f7628f38a314f74a9b9b - languageName: node - linkType: hard - -"@types/yargs@npm:^17.0.0": - version: 17.0.33 - resolution: "@types/yargs@npm:17.0.33" - dependencies: - "@types/yargs-parser": "npm:*" - checksum: 10/16f6681bf4d99fb671bf56029141ed01db2862e3db9df7fc92d8bea494359ac96a1b4b1c35a836d1e95e665fb18ad753ab2015fc0db663454e8fd4e5d5e2ef91 - languageName: node - linkType: hard - -"abbrev@npm:^3.0.0": - version: 3.0.1 - resolution: "abbrev@npm:3.0.1" - checksum: 10/ebd2c149dda6f543b66ce3779ea612151bb3aa9d0824f169773ee9876f1ca5a4e0adbcccc7eed048c04da7998e1825e2aa76fcca92d9e67dea50ac2b0a58dc2e - languageName: node - linkType: hard - -"accepts@npm:^1.3.8": - version: 1.3.8 - resolution: "accepts@npm:1.3.8" - dependencies: - mime-types: "npm:~2.1.34" - negotiator: "npm:0.6.3" - checksum: 10/67eaaa90e2917c58418e7a9b89392002d2b1ccd69bcca4799135d0c632f3b082f23f4ae4ddeedbced5aa59bcc7bdf4699c69ebed4593696c922462b7bc5744d6 - languageName: node - linkType: hard - -"acorn-walk@npm:^8.1.1": - version: 8.3.4 - resolution: "acorn-walk@npm:8.3.4" - dependencies: - acorn: "npm:^8.11.0" - checksum: 10/871386764e1451c637bb8ab9f76f4995d408057e9909be6fb5ad68537ae3375d85e6a6f170b98989f44ab3ff6c74ad120bc2779a3d577606e7a0cd2b4efcaf77 - languageName: node - linkType: hard - -"acorn@npm:^8.11.0, acorn@npm:^8.4.1": - version: 8.15.0 - resolution: "acorn@npm:8.15.0" - bin: - acorn: bin/acorn - checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 - languageName: node - linkType: hard - -"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": - version: 7.1.4 - resolution: "agent-base@npm:7.1.4" - checksum: 10/79bef167247789f955aaba113bae74bf64aa1e1acca4b1d6bb444bdf91d82c3e07e9451ef6a6e2e35e8f71a6f97ce33e3d855a5328eb9fad1bc3cc4cfd031ed8 - languageName: node - linkType: hard - -"ansi-escapes@npm:^1.0.0, ansi-escapes@npm:^1.1.0": - version: 1.4.0 - resolution: "ansi-escapes@npm:1.4.0" - checksum: 10/287f18ea70cde710dbb83b6b6c4e1d62fcb962b951a601d976df69478a4ebdff6305691e3befb9053d740060544929732b8bade7a9781611dcd2b997e6bda3d6 - languageName: node - linkType: hard - -"ansi-regex@npm:^2.0.0": - version: 2.1.1 - resolution: "ansi-regex@npm:2.1.1" - checksum: 10/190abd03e4ff86794f338a31795d262c1dfe8c91f7e01d04f13f646f1dcb16c5800818f886047876f1272f065570ab86b24b99089f8b68a0e11ff19aed4ca8f1 - languageName: node - linkType: hard - -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 10/2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b - languageName: node - linkType: hard - -"ansi-regex@npm:^6.0.1": - version: 6.2.2 - resolution: "ansi-regex@npm:6.2.2" - checksum: 10/9b17ce2c6daecc75bcd5966b9ad672c23b184dc3ed9bf3c98a0702f0d2f736c15c10d461913568f2cf527a5e64291c7473358885dd493305c84a1cfed66ba94f - languageName: node - linkType: hard - -"ansi-styles@npm:^2.2.1": - version: 2.2.1 - resolution: "ansi-styles@npm:2.2.1" - checksum: 10/ebc0e00381f2a29000d1dac8466a640ce11943cef3bda3cd0020dc042e31e1058ab59bf6169cd794a54c3a7338a61ebc404b7c91e004092dd20e028c432c9c2c - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10/b4494dfbfc7e4591b4711a396bd27e540f8153914123dccb4cdbbcb514015ada63a3809f362b9d8d4f6b17a706f1d7bea3c6f974b15fa5ae76b5b502070889ff - languageName: node - linkType: hard - -"ansi-styles@npm:^6.1.0": - version: 6.2.3 - resolution: "ansi-styles@npm:6.2.3" - checksum: 10/c49dad7639f3e48859bd51824c93b9eb0db628afc243c51c3dd2410c4a15ede1a83881c6c7341aa2b159c4f90c11befb38f2ba848c07c66c9f9de4bcd7cb9f30 - languageName: node - linkType: hard - -"anymatch@npm:~3.1.2": - version: 3.1.3 - resolution: "anymatch@npm:3.1.3" - dependencies: - normalize-path: "npm:^3.0.0" - picomatch: "npm:^2.0.4" - checksum: 10/3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 - languageName: node - linkType: hard - -"arg@npm:^4.1.0": - version: 4.1.3 - resolution: "arg@npm:4.1.3" - checksum: 10/969b491082f20cad166649fa4d2073ea9e974a4e5ac36247ca23d2e5a8b3cb12d60e9ff70a8acfe26d76566c71fd351ee5e6a9a6595157eb36f92b1fd64e1599 - languageName: node - linkType: hard - -"argparse@npm:^2.0.1": - version: 2.0.1 - resolution: "argparse@npm:2.0.1" - checksum: 10/18640244e641a417ec75a9bd38b0b2b6b95af5199aa241b131d4b2fb206f334d7ecc600bd194861610a5579084978bfcbb02baa399dbe442d56d0ae5e60dbaef - languageName: node - linkType: hard - -"asap@npm:^2.0.0": - version: 2.0.6 - resolution: "asap@npm:2.0.6" - checksum: 10/b244c0458c571945e4b3be0b14eb001bea5596f9868cc50cc711dc03d58a7e953517d3f0dad81ccde3ff37d1f074701fa76a6f07d41aaa992d7204a37b915dda - languageName: node - linkType: hard - -"async-function@npm:^1.0.0": - version: 1.0.0 - resolution: "async-function@npm:1.0.0" - checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd - languageName: node - linkType: hard - -"async-generator-function@npm:^1.0.0": - version: 1.0.0 - resolution: "async-generator-function@npm:1.0.0" - checksum: 10/3d49e7acbeee9e84537f4cb0e0f91893df8eba976759875ae8ee9e3d3c82f6ecdebdb347c2fad9926b92596d93cdfc78ecc988bcdf407e40433e8e8e6fe5d78e - languageName: node - linkType: hard - -"babel-polyfill@npm:^6.3.14": - version: 6.26.0 - resolution: "babel-polyfill@npm:6.26.0" - dependencies: - babel-runtime: "npm:^6.26.0" - core-js: "npm:^2.5.0" - regenerator-runtime: "npm:^0.10.5" - checksum: 10/2abfe4bf4af39c7b0c42af8ccce14897aefbde6547a227e36f4f12ba5795e8603d2964cc72ceb59086b5a69fafcb00b0deda5c1055e373c3bef76dcc517d6d0d - languageName: node - linkType: hard - -"babel-runtime@npm:^6.26.0": - version: 6.26.0 - resolution: "babel-runtime@npm:6.26.0" - dependencies: - core-js: "npm:^2.4.0" - regenerator-runtime: "npm:^0.11.0" - checksum: 10/2cdf0f083b9598a43cdb11cbf1e7060584079a9a2230f06aec997ba81e887ef17fdcb5ad813a484ee099e06d2de0cea832bdd3011c06325acb284284c754ee8f - languageName: node - linkType: hard - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10/9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 - languageName: node - linkType: hard - -"binary-extensions@npm:^2.0.0": - version: 2.3.0 - resolution: "binary-extensions@npm:2.3.0" - checksum: 10/bcad01494e8a9283abf18c1b967af65ee79b0c6a9e6fcfafebfe91dbe6e0fc7272bafb73389e198b310516ae04f7ad17d79aacf6cb4c0d5d5202a7e2e52c7d98 - languageName: node - linkType: hard - -"brace-expansion@npm:^1.1.7": - version: 1.1.12 - resolution: "brace-expansion@npm:1.1.12" - dependencies: - balanced-match: "npm:^1.0.0" - concat-map: "npm:0.0.1" - checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.1": - version: 2.0.2 - resolution: "brace-expansion@npm:2.0.2" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10/01dff195e3646bc4b0d27b63d9bab84d2ebc06121ff5013ad6e5356daa5a9d6b60fa26cf73c74797f2dc3fbec112af13578d51f75228c1112b26c790a87b0488 - languageName: node - linkType: hard - -"braces@npm:~3.0.2": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 10/fad11a0d4697a27162840b02b1fad249c1683cbc510cd5bf1a471f2f8085c046d41094308c577a50a03a579dd99d5a6b3724c4b5e8b14df2c4443844cfcda2c6 - languageName: node - linkType: hard - -"builtins@npm:^5.0.0": - version: 5.1.0 - resolution: "builtins@npm:5.1.0" - dependencies: - semver: "npm:^7.0.0" - checksum: 10/60aa9969f69656bf6eab82cd74b23ab805f112ae46a54b912bccc1533875760f2d2ce95e0a7d13144e35ada9f0386f17ed4961908bc9434b5a5e21375b1902b2 - languageName: node - linkType: hard - -"bytes@npm:3.1.2": - version: 3.1.2 - resolution: "bytes@npm:3.1.2" - checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 - languageName: node - linkType: hard - -"cacache@npm:^19.0.1": - version: 19.0.1 - resolution: "cacache@npm:19.0.1" - dependencies: - "@npmcli/fs": "npm:^4.0.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^7.0.2" - ssri: "npm:^12.0.0" - tar: "npm:^7.4.3" - unique-filename: "npm:^4.0.0" - checksum: 10/ea026b27b13656330c2bbaa462a88181dcaa0435c1c2e705db89b31d9bdf7126049d6d0445ba746dca21454a0cfdf1d6f47fd39d34c8c8435296b30bc5738a13 - languageName: node - linkType: hard - -"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": - version: 1.0.2 - resolution: "call-bind-apply-helpers@npm:1.0.2" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - checksum: 10/00482c1f6aa7cfb30fb1dbeb13873edf81cfac7c29ed67a5957d60635a56b2a4a480f1016ddbdb3395cc37900d46037fb965043a51c5c789ffeab4fc535d18b5 - languageName: node - linkType: hard - -"call-bound@npm:^1.0.2": - version: 1.0.4 - resolution: "call-bound@npm:1.0.4" - dependencies: - call-bind-apply-helpers: "npm:^1.0.2" - get-intrinsic: "npm:^1.3.0" - checksum: 10/ef2b96e126ec0e58a7ff694db43f4d0d44f80e641370c21549ed911fecbdbc2df3ebc9bddad918d6bbdefeafb60bb3337902006d5176d72bcd2da74820991af7 - languageName: node - linkType: hard - -"chalk@npm:^1.0.0, chalk@npm:^1.1.0": - version: 1.1.3 - resolution: "chalk@npm:1.1.3" - dependencies: - ansi-styles: "npm:^2.2.1" - escape-string-regexp: "npm:^1.0.2" - has-ansi: "npm:^2.0.0" - strip-ansi: "npm:^3.0.0" - supports-color: "npm:^2.0.0" - checksum: 10/abcf10da02afde04cc615f06c4bdb3ffc70d2bfbf37e0df03bb88b7459a9411dab4d01210745b773abc936031530a20355f1facc4bee1bbf08613d8fdcfb3aeb - languageName: node - linkType: hard - -"chalk@npm:^4.1.1": - version: 4.1.2 - resolution: "chalk@npm:4.1.2" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10/cb3f3e594913d63b1814d7ca7c9bafbf895f75fbf93b92991980610dfd7b48500af4e3a5d4e3a8f337990a96b168d7eb84ee55efdce965e2ee8efc20f8c8f139 - languageName: node - linkType: hard - -"chokidar@npm:^3.5.2": - version: 3.6.0 - resolution: "chokidar@npm:3.6.0" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 10/c327fb07704443f8d15f7b4a7ce93b2f0bc0e6cea07ec28a7570aa22cd51fcf0379df589403976ea956c369f25aa82d84561947e227cd925902e1751371658df - languageName: node - linkType: hard - -"chownr@npm:^3.0.0": - version: 3.0.0 - resolution: "chownr@npm:3.0.0" - checksum: 10/b63cb1f73d171d140a2ed8154ee6566c8ab775d3196b0e03a2a94b5f6a0ce7777ee5685ca56849403c8d17bd457a6540672f9a60696a6137c7a409097495b82c - languageName: node - linkType: hard - -"cli-cursor@npm:^1.0.1, cli-cursor@npm:^1.0.2": - version: 1.0.2 - resolution: "cli-cursor@npm:1.0.2" - dependencies: - restore-cursor: "npm:^1.0.1" - checksum: 10/e3b4400d5e925ed11c7596f82e80e170693f69ac6f0f21da2a400043c37548dd780f985a1a5ef1ffb038e36fc6711d1d4f066b104eed851ae76e34bd883cf2bf - languageName: node - linkType: hard - -"cli-width@npm:^1.0.1": - version: 1.1.1 - resolution: "cli-width@npm:1.1.1" - checksum: 10/b85b74a9b6ec288a9bb594dbac7358374a07c8038544f2aebb0a248073ee485ab68c7eb11ebf162031aca4f8cafac931eefa9476cbf321fe5ee961fc6f3497a7 - languageName: node - linkType: hard - -"co-body@npm:^6.1.0": - version: 6.2.0 - resolution: "co-body@npm:6.2.0" - dependencies: - "@hapi/bourne": "npm:^3.0.0" - inflation: "npm:^2.0.0" - qs: "npm:^6.5.2" - raw-body: "npm:^2.3.3" - type-is: "npm:^1.6.16" - checksum: 10/644761ad8abbcbc15f0a76634b17abda928fec01aa7bfdee23f4e65c0d49c6ea63738d1ed7fca1f92a52bd76cd08f8031d788a65ab00842744d50f03536c7b36 - languageName: node - linkType: hard - -"code-point-at@npm:^1.0.0": - version: 1.1.0 - resolution: "code-point-at@npm:1.1.0" - checksum: 10/17d5666611f9b16d64fdf48176d9b7fb1c7d1c1607a189f7e600040a11a6616982876af148230336adb7d8fe728a559f743a4e29db3747e3b1a32fa7f4529681 - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10/fa00c91b4332b294de06b443923246bccebe9fab1b253f7fe1772d37b06a2269b4039a85e309abe1fe11b267b11c08d1d0473fda3badd6167f57313af2887a64 - languageName: node - linkType: hard - -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 - languageName: node - linkType: hard - -"compare-versions@npm:^3.6.0": - version: 3.6.0 - resolution: "compare-versions@npm:3.6.0" - checksum: 10/7492a50cdaa2c27f5254eee7c4b38856e1c164991bab3d98d7fd067fe4b570d47123ecb92523b78338be86aa221668fd3868bfe8caa5587dc3ebbe1a03d52b5d - languageName: node - linkType: hard - -"concat-map@npm:0.0.1": - version: 0.0.1 - resolution: "concat-map@npm:0.0.1" - checksum: 10/9680699c8e2b3af0ae22592cb764acaf973f292a7b71b8a06720233011853a58e256c89216a10cbe889727532fd77f8bcd49a760cedfde271b8e006c20e079f2 - languageName: node - linkType: hard - -"content-disposition@npm:~0.5.4": - version: 0.5.4 - resolution: "content-disposition@npm:0.5.4" - dependencies: - safe-buffer: "npm:5.2.1" - checksum: 10/b7f4ce176e324f19324be69b05bf6f6e411160ac94bc523b782248129eb1ef3be006f6cff431aaea5e337fe5d176ce8830b8c2a1b721626ead8933f0cbe78720 - languageName: node - linkType: hard - -"content-type@npm:^1.0.5": - version: 1.0.5 - resolution: "content-type@npm:1.0.5" - checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 - languageName: node - linkType: hard - -"cookies@npm:~0.9.1": - version: 0.9.1 - resolution: "cookies@npm:0.9.1" - dependencies: - depd: "npm:~2.0.0" - keygrip: "npm:~1.1.0" - checksum: 10/4816461a38d907b20f3fb7a2bc4741fe580e7a195f3e248ef7025cb3be56a07638a0f4e72553a5f535554ca30172c8a3245c63ac72c9737cec034e9a47773392 - languageName: node - linkType: hard - -"core-js@npm:^2.4.0, core-js@npm:^2.5.0": - version: 2.6.12 - resolution: "core-js@npm:2.6.12" - checksum: 10/7c624eb00a59c74c769d5d80f751f3bf1fc6201205b6562f27286ad5e00bbca1483f2f7eb0c2854b86f526ef5c7dc958b45f2ff536f8a31b8e9cb1a13a96efca - languageName: node - linkType: hard - -"create-require@npm:^1.1.0": - version: 1.1.1 - resolution: "create-require@npm:1.1.1" - checksum: 10/a9a1503d4390d8b59ad86f4607de7870b39cad43d929813599a23714831e81c520bddf61bcdd1f8e30f05fd3a2b71ae8538e946eb2786dc65c2bbc520f692eff - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.6": - version: 7.0.6 - resolution: "cross-spawn@npm:7.0.6" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10/0d52657d7ae36eb130999dffff1168ec348687b48dd38e2ff59992ed916c88d328cf1d07ff4a4a10bc78de5e1c23f04b306d569e42f7a2293915c081e4dfee86 - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.3.4, debug@npm:^4.4.1": - version: 4.4.3 - resolution: "debug@npm:4.4.3" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad - languageName: node - linkType: hard - -"deep-equal@npm:~1.0.1": - version: 1.0.1 - resolution: "deep-equal@npm:1.0.1" - checksum: 10/cbecc071afb2891334ced9e9de5834889b9a9992ae8d8369b7eb74c513529eb6d1f6c04d4e2b5f34d8386f7816cd7a6cda45edff847695faea45e43c23973f45 - languageName: node - linkType: hard - -"delegates@npm:^1.0.0": - version: 1.0.0 - resolution: "delegates@npm:1.0.0" - checksum: 10/a51744d9b53c164ba9c0492471a1a2ffa0b6727451bdc89e31627fdf4adda9d51277cfcbfb20f0a6f08ccb3c436f341df3e92631a3440226d93a8971724771fd - languageName: node - linkType: hard - -"depd@npm:2.0.0, depd@npm:~2.0.0": - version: 2.0.0 - resolution: "depd@npm:2.0.0" - checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca - languageName: node - linkType: hard - -"depd@npm:~1.1.2": - version: 1.1.2 - resolution: "depd@npm:1.1.2" - checksum: 10/2ed6966fc14463a9e85451db330ab8ba041efed0b9a1a472dbfc6fbf2f82bab66491915f996b25d8517dddc36c8c74e24c30879b34877f3c4410733444a51d1d - languageName: node - linkType: hard - -"destroy@npm:^1.2.0": - version: 1.2.0 - resolution: "destroy@npm:1.2.0" - checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 - languageName: node - linkType: hard - -"dezalgo@npm:^1.0.4": - version: 1.0.4 - resolution: "dezalgo@npm:1.0.4" - dependencies: - asap: "npm:^2.0.0" - wrappy: "npm:1" - checksum: 10/895389c6aead740d2ab5da4d3466d20fa30f738010a4d3f4dcccc9fc645ca31c9d10b7e1804ae489b1eb02c7986f9f1f34ba132d409b043082a86d9a4e745624 - languageName: node - linkType: hard - -"diff@npm:^4.0.1": - version: 4.0.2 - resolution: "diff@npm:4.0.2" - checksum: 10/ec09ec2101934ca5966355a229d77afcad5911c92e2a77413efda5455636c4cf2ce84057e2d7715227a2eeeda04255b849bd3ae3a4dd22eb22e86e76456df069 - languageName: node - linkType: hard - -"dotenv@npm:^10.0.0": - version: 10.0.0 - resolution: "dotenv@npm:10.0.0" - checksum: 10/55f701ae213e3afe3f4232fae5edfb6e0c49f061a363ff9f1c5a0c2bf3fb990a6e49aeada11b2a116efb5fdc3bc3f1ef55ab330be43033410b267f7c0809a9dc - languageName: node - linkType: hard - -"dunder-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "dunder-proto@npm:1.0.1" - dependencies: - call-bind-apply-helpers: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.2.0" - checksum: 10/5add88a3d68d42d6e6130a0cac450b7c2edbe73364bbd2fc334564418569bea97c6943a8fcd70e27130bf32afc236f30982fc4905039b703f23e9e0433c29934 - languageName: node - linkType: hard - -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10/9b1d3e1baefeaf7d70799db8774149cef33b97183a6addceeba0cf6b85ba23ee2686f302f14482006df32df75d32b17c509c143a3689627929e4a8efaf483952 - languageName: node - linkType: hard - -"ee-first@npm:1.1.1": - version: 1.1.1 - resolution: "ee-first@npm:1.1.1" - checksum: 10/1b4cac778d64ce3b582a7e26b218afe07e207a0f9bfe13cc7395a6d307849cfe361e65033c3251e00c27dd060cab43014c2d6b2647676135e18b77d2d05b3f4f - languageName: node - linkType: hard - -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10/c72d67a6821be15ec11997877c437491c313d924306b8da5d87d2a2bcc2cec9903cb5b04ee1a088460501d8e5b44f10df82fdc93c444101a7610b80c8b6938e1 - languageName: node - linkType: hard - -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 10/915acf859cea7131dac1b2b5c9c8e35c4849e325a1d114c30adb8cd615970f6dca0e27f64f3a4949d7d6ed86ecd79a1c5c63f02e697513cddd7b5835c90948b8 - languageName: node - linkType: hard - -"encodeurl@npm:^2.0.0": - version: 2.0.0 - resolution: "encodeurl@npm:2.0.0" - checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe - languageName: node - linkType: hard - -"encoding@npm:^0.1.13": - version: 0.1.13 - resolution: "encoding@npm:0.1.13" - dependencies: - iconv-lite: "npm:^0.6.2" - checksum: 10/bb98632f8ffa823996e508ce6a58ffcf5856330fde839ae42c9e1f436cc3b5cc651d4aeae72222916545428e54fd0f6aa8862fd8d25bdbcc4589f1e3f3715e7f - languageName: node - linkType: hard - -"entities@npm:^4.4.0": - version: 4.5.0 - resolution: "entities@npm:4.5.0" - checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48 - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10/65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e - languageName: node - linkType: hard - -"err-code@npm:^2.0.2": - version: 2.0.3 - resolution: "err-code@npm:2.0.3" - checksum: 10/1d20d825cdcce8d811bfbe86340f4755c02655a7feb2f13f8c880566d9d72a3f6c92c192a6867632e490d6da67b678271f46e01044996a6443e870331100dfdd - languageName: node - linkType: hard - -"es-define-property@npm:^1.0.1": - version: 1.0.1 - resolution: "es-define-property@npm:1.0.1" - checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 - languageName: node - linkType: hard - -"es-errors@npm:^1.3.0": - version: 1.3.0 - resolution: "es-errors@npm:1.3.0" - checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 - languageName: node - linkType: hard - -"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": - version: 1.1.1 - resolution: "es-object-atoms@npm:1.1.1" - dependencies: - es-errors: "npm:^1.3.0" - checksum: 10/54fe77de288451dae51c37bfbfe3ec86732dc3778f98f3eb3bdb4bf48063b2c0b8f9c93542656986149d08aa5be3204286e2276053d19582b76753f1a2728867 - languageName: node - linkType: hard - -"escape-html@npm:^1.0.3": - version: 1.0.3 - resolution: "escape-html@npm:1.0.3" - checksum: 10/6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5": - version: 1.0.5 - resolution: "escape-string-regexp@npm:1.0.5" - checksum: 10/6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410 - languageName: node - linkType: hard - -"exit-hook@npm:^1.0.0": - version: 1.1.1 - resolution: "exit-hook@npm:1.1.1" - checksum: 10/1b4f16da7c202cd336ca07acb052922639182b4e2f1ad4007ed481bb774ce93469f505dec1371d9cd580ac54146a9fd260f053b0e4a48fa87c49fa3dc4a3f144 - languageName: node - linkType: hard - -"exponential-backoff@npm:^3.1.1": - version: 3.1.3 - resolution: "exponential-backoff@npm:3.1.3" - checksum: 10/ca25962b4bbab943b7c4ed0b5228e263833a5063c65e1cdeac4be9afad350aae5466e8e619b5051f4f8d37b2144a2d6e8fcc771b6cc82934f7dade2f964f652c - languageName: node - linkType: hard - -"fdir@npm:^6.5.0": - version: 6.5.0 - resolution: "fdir@npm:6.5.0" - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - checksum: 10/14ca1c9f0a0e8f4f2e9bf4e8551065a164a09545dae548c12a18d238b72e51e5a7b39bd8e5494b56463a0877672d0a6c1ef62c6fa0677db1b0c847773be939b1 - languageName: node - linkType: hard - -"figures@npm:^1.3.5": - version: 1.7.0 - resolution: "figures@npm:1.7.0" - dependencies: - escape-string-regexp: "npm:^1.0.5" - object-assign: "npm:^4.1.0" - checksum: 10/3a815f8a3b488f818e661694112b4546ddff799aa6a07c864c46dadff923af74021f84d42ded402432a98c3208acebf2d096f3a7cc3d1a7b19a2cdc9cbcaea2e - languageName: node - linkType: hard - -"fill-range@npm:^7.1.1": - version: 7.1.1 - resolution: "fill-range@npm:7.1.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: 10/a7095cb39e5bc32fada2aa7c7249d3f6b01bd1ce461a61b0adabacccabd9198500c6fb1f68a7c851a657e273fce2233ba869638897f3d7ed2e87a2d89b4436ea - languageName: node - linkType: hard - -"foreground-child@npm:^3.1.0": - version: 3.3.1 - resolution: "foreground-child@npm:3.3.1" - dependencies: - cross-spawn: "npm:^7.0.6" - signal-exit: "npm:^4.0.1" - checksum: 10/427b33f997a98073c0424e5c07169264a62cda806d8d2ded159b5b903fdfc8f0a1457e06b5fc35506497acb3f1e353f025edee796300209ac6231e80edece835 - languageName: node - linkType: hard - -"formidable@npm:^2.0.1": - version: 2.1.5 - resolution: "formidable@npm:2.1.5" - dependencies: - "@paralleldrive/cuid2": "npm:^2.2.2" - dezalgo: "npm:^1.0.4" - once: "npm:^1.4.0" - qs: "npm:^6.11.0" - checksum: 10/ee96de12e91d63fe86479ffe5bf59004bb3f43e00ce7ccecd1b1ff10b5d1a89a19b1ede727e1fe57ef596c377b9f9300212a5f7bab14fd28f3c4ffe12dbb4cc7 - languageName: node - linkType: hard - -"fresh@npm:~0.5.2": - version: 0.5.2 - resolution: "fresh@npm:0.5.2" - checksum: 10/64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1 - languageName: node - linkType: hard - -"fromentries@npm:^1.3.2": - version: 1.3.2 - resolution: "fromentries@npm:1.3.2" - checksum: 10/10d6e07d289db102c0c1eaf5c3e3fa55ddd6b50033d7de16d99a7cd89f1e1a302dfadb26457031f9bb5d2ed95a179aaf0396092dde5abcae06e8a2f0476826be - languageName: node - linkType: hard - -"fs-minipass@npm:^3.0.0": - version: 3.0.3 - resolution: "fs-minipass@npm:3.0.3" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10/af143246cf6884fe26fa281621d45cfe111d34b30535a475bfa38dafe343dadb466c047a924ffc7d6b7b18265df4110224ce3803806dbb07173bf2087b648d7f - languageName: node - linkType: hard - -"fsevents@npm:~2.3.2": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: "npm:latest" - checksum: 10/4c1ade961ded57cdbfbb5cac5106ec17bc8bccd62e16343c569a0ceeca83b9dfef87550b4dc5cbb89642da412b20c5071f304c8c464b80415446e8e155a038c0 - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 - languageName: node - linkType: hard - -"generator-function@npm:^2.0.0": - version: 2.0.1 - resolution: "generator-function@npm:2.0.1" - checksum: 10/eb7e7eb896c5433f3d40982b2ccacdb3dd990dd3499f14040e002b5d54572476513be8a2e6f9609f6e41ab29f2c4469307611ddbfc37ff4e46b765c326663805 - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0": - version: 1.3.1 - resolution: "get-intrinsic@npm:1.3.1" - dependencies: - async-function: "npm:^1.0.0" - async-generator-function: "npm:^1.0.0" - call-bind-apply-helpers: "npm:^1.0.2" - es-define-property: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.1.1" - function-bind: "npm:^1.1.2" - generator-function: "npm:^2.0.0" - get-proto: "npm:^1.0.1" - gopd: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - hasown: "npm:^2.0.2" - math-intrinsics: "npm:^1.1.0" - checksum: 10/bb579dda84caa4a3a41611bdd483dade7f00f246f2a7992eb143c5861155290df3fdb48a8406efa3dfb0b434e2c8fafa4eebd469e409d0439247f85fc3fa2cc1 - languageName: node - linkType: hard - -"get-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "get-proto@npm:1.0.1" - dependencies: - dunder-proto: "npm:^1.0.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10/4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b - languageName: node - linkType: hard - -"glob-parent@npm:~5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: 10/32cd106ce8c0d83731966d31517adb766d02c3812de49c30cfe0675c7c0ae6630c11214c54a5ae67aca882cf738d27fd7768f21aa19118b9245950554be07247 - languageName: node - linkType: hard - -"glob@npm:^10.2.2": - version: 10.4.5 - resolution: "glob@npm:10.4.5" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10/698dfe11828b7efd0514cd11e573eaed26b2dff611f0400907281ce3eab0c1e56143ef9b35adc7c77ecc71fba74717b510c7c223d34ca8a98ec81777b293d4ac - languageName: node - linkType: hard - -"gopd@npm:^1.2.0": - version: 1.2.0 - resolution: "gopd@npm:1.2.0" - checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 - languageName: node - linkType: hard - -"graceful-fs@npm:^4.2.6": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 - languageName: node - linkType: hard - -"has-ansi@npm:^2.0.0": - version: 2.0.0 - resolution: "has-ansi@npm:2.0.0" - dependencies: - ansi-regex: "npm:^2.0.0" - checksum: 10/1b51daa0214440db171ff359d0a2d17bc20061164c57e76234f614c91dbd2a79ddd68dfc8ee73629366f7be45a6df5f2ea9de83f52e1ca24433f2cc78c35d8ec - languageName: node - linkType: hard - -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10/261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad - languageName: node - linkType: hard - -"has-symbols@npm:^1.1.0": - version: 1.1.0 - resolution: "has-symbols@npm:1.1.0" - checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa - languageName: node - linkType: hard - -"hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10/7898a9c1788b2862cf0f9c345a6bec77ba4a0c0983c7f19d610c382343d4f98fa260686b225dfb1f88393a66679d2ec58ee310c1d6868c081eda7918f32cc70a - languageName: node - linkType: hard - -"http-assert@npm:^1.5.0": - version: 1.5.0 - resolution: "http-assert@npm:1.5.0" - dependencies: - deep-equal: "npm:~1.0.1" - http-errors: "npm:~1.8.0" - checksum: 10/69c9b3c14cf8b2822916360a365089ce936c883c49068f91c365eccba5c141a9964d19fdda589150a480013bf503bf37d8936c732e9635819339e730ab0e7527 - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.1": - version: 4.2.0 - resolution: "http-cache-semantics@npm:4.2.0" - checksum: 10/4efd2dfcfeea9d5e88c84af450b9980be8a43c2c8179508b1c57c7b4421c855f3e8efe92fa53e0b3f4a43c85824ada930eabbc306d1b3beab750b6dcc5187693 - languageName: node - linkType: hard - -"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": - version: 2.0.0 - resolution: "http-errors@npm:2.0.0" - dependencies: - depd: "npm:2.0.0" - inherits: "npm:2.0.4" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - toidentifier: "npm:1.0.1" - checksum: 10/0e7f76ee8ff8a33e58a3281a469815b893c41357378f408be8f6d4aa7d1efafb0da064625518e7078381b6a92325949b119dc38fcb30bdbc4e3a35f78c44c439 - languageName: node - linkType: hard - -"http-errors@npm:~1.8.0": - version: 1.8.1 - resolution: "http-errors@npm:1.8.1" - dependencies: - depd: "npm:~1.1.2" - inherits: "npm:2.0.4" - setprototypeof: "npm:1.2.0" - statuses: "npm:>= 1.5.0 < 2" - toidentifier: "npm:1.0.1" - checksum: 10/76fc491bd8df2251e21978e080d5dae20d9736cfb29bb72b5b76ec1bcebb1c14f0f58a3a128dd89288934379d2173cfb0421c571d54103e93dd65ef6243d64d8 - languageName: node - linkType: hard - -"http-proxy-agent@npm:^7.0.0": - version: 7.0.2 - resolution: "http-proxy-agent@npm:7.0.2" - dependencies: - agent-base: "npm:^7.1.0" - debug: "npm:^4.3.4" - checksum: 10/d062acfa0cb82beeb558f1043c6ba770ea892b5fb7b28654dbc70ea2aeea55226dd34c02a294f6c1ca179a5aa483c4ea641846821b182edbd9cc5d89b54c6848 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.1": - version: 7.0.6 - resolution: "https-proxy-agent@npm:7.0.6" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:4" - checksum: 10/784b628cbd55b25542a9d85033bdfd03d4eda630fb8b3c9477959367f3be95dc476ed2ecbb9836c359c7c698027fc7b45723a302324433590f45d6c1706e8c13 - languageName: node - linkType: hard - -"iconv-lite@npm:0.4.24": - version: 0.4.24 - resolution: "iconv-lite@npm:0.4.24" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3 - languageName: node - linkType: hard - -"iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10/24e3292dd3dadaa81d065c6f8c41b274a47098150d444b96e5f53b4638a9a71482921ea6a91a1f59bb71d9796de25e04afd05919fa64c360347ba65d3766f10f - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10/2d30b157a91fe1c1d7c6f653cbf263f039be6c5bfa959245a16d4ee191fc0f2af86c08545b6e6beeb041c56b574d2d5b9f95343d378ab49c0f37394d541e7fc8 - languageName: node - linkType: hard - -"in-publish@npm:^2.0.0": - version: 2.0.1 - resolution: "in-publish@npm:2.0.1" - bin: - in-install: in-install.js - in-publish: in-publish.js - not-in-install: not-in-install.js - not-in-publish: not-in-publish.js - checksum: 10/e02c5812669f37adaf4c3cea43854aae0746e8987a8a455dbcb03e4cb83059aba60e522cf053fc027ab943123e71ccf5121bc2924dc6d8b73f34020849414e20 - languageName: node - linkType: hard - -"inflation@npm:^2.0.0": - version: 2.1.0 - resolution: "inflation@npm:2.1.0" - checksum: 10/80c1b5d9ec408105a85f0623c824d668ddf0cadafd8d9716c0737990e5a712ae5f7d6bb0ff216b6648eccb9c6ac69fe06c0d8c58456d168db5bf550c89dd74ed - languageName: node - linkType: hard - -"inherits@npm:2.0.4": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 - languageName: node - linkType: hard - -"inquirer@npm:0.11.0": - version: 0.11.0 - resolution: "inquirer@npm:0.11.0" - dependencies: - ansi-escapes: "npm:^1.1.0" - ansi-regex: "npm:^2.0.0" - chalk: "npm:^1.0.0" - cli-cursor: "npm:^1.0.1" - cli-width: "npm:^1.0.1" - figures: "npm:^1.3.5" - lodash: "npm:^3.3.1" - readline2: "npm:^1.0.1" - run-async: "npm:^0.1.0" - rx-lite: "npm:^3.1.2" - strip-ansi: "npm:^3.0.0" - through: "npm:^2.3.6" - checksum: 10/3274e6aa1c570d219e4be5a02f7ae758dc99ad7b90a54239749721be5e66fb1371b55be69b8155995cdb476667890d96089a6419e069634f5d8aa95ac719cff3 - languageName: node - linkType: hard - -"ip-address@npm:^10.0.1": - version: 10.0.1 - resolution: "ip-address@npm:10.0.1" - checksum: 10/09731acda32cd8e14c46830c137e7e5940f47b36d63ffb87c737331270287d631cf25aa95570907a67d3f919fdb25f4470c404eda21e62f22e0a55927f4dd0fb - languageName: node - linkType: hard - -"is-binary-path@npm:~2.1.0": - version: 2.1.0 - resolution: "is-binary-path@npm:2.1.0" - dependencies: - binary-extensions: "npm:^2.0.0" - checksum: 10/078e51b4f956c2c5fd2b26bb2672c3ccf7e1faff38e0ebdba45612265f4e3d9fc3127a1fa8370bbf09eab61339203c3d3b7af5662cbf8be4030f8fac37745b0e - languageName: node - linkType: hard - -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 10/df033653d06d0eb567461e58a7a8c9f940bd8c22274b94bf7671ab36df5719791aae15eef6d83bbb5e23283967f2f984b8914559d4449efda578c775c4be6f85 - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^1.0.0": - version: 1.0.0 - resolution: "is-fullwidth-code-point@npm:1.0.0" - dependencies: - number-is-nan: "npm:^1.0.0" - checksum: 10/4d46a7465a66a8aebcc5340d3b63a56602133874af576a9ca42c6f0f4bd787a743605771c5f246db77da96605fefeffb65fc1dbe862dcc7328f4b4d03edf5a57 - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10/44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 - languageName: node - linkType: hard - -"is-glob@npm:^4.0.1, is-glob@npm:~4.0.1": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 10/3ed74f2b0cdf4f401f38edb0442ddfde3092d79d7d35c9919c86641efdbcbb32e45aa3c0f70ce5eecc946896cd5a0f26e4188b9f2b881876f7cb6c505b82da11 - languageName: node - linkType: hard - -"is-number@npm:^7.0.0": - version: 7.0.0 - resolution: "is-number@npm:7.0.0" - checksum: 10/6a6c3383f68afa1e05b286af866017c78f1226d43ac8cb064e115ff9ed85eb33f5c4f7216c96a71e4dfea289ef52c5da3aef5bbfade8ffe47a0465d70c0c8e86 - languageName: node - linkType: hard - -"isexe@npm:^2.0.0": - version: 2.0.0 - resolution: "isexe@npm:2.0.0" - checksum: 10/7c9f715c03aff08f35e98b1fadae1b9267b38f0615d501824f9743f3aab99ef10e303ce7db3f186763a0b70a19de5791ebfc854ff884d5a8c4d92211f642ec92 - languageName: node - linkType: hard - -"isexe@npm:^3.1.1": - version: 3.1.1 - resolution: "isexe@npm:3.1.1" - checksum: 10/7fe1931ee4e88eb5aa524cd3ceb8c882537bc3a81b02e438b240e47012eef49c86904d0f0e593ea7c3a9996d18d0f1f3be8d3eaa92333977b0c3a9d353d5563e - languageName: node - linkType: hard - -"isomorphic-unfetch@npm:^3.1.0": - version: 3.1.0 - resolution: "isomorphic-unfetch@npm:3.1.0" - dependencies: - node-fetch: "npm:^2.6.1" - unfetch: "npm:^4.2.0" - checksum: 10/4e760d9a3f94b42c59fe5c6b53202469cecd864875dcac927668b1f43eb57698422a0086fadde47f7815752c4f4e30ecf1ce9a0eb09c44a871a2484dbc580b39 - languageName: node - linkType: hard - -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10/96f8786eaab98e4bf5b2a5d6d9588ea46c4d06bbc4f2eb861fdd7b6b182b16f71d8a70e79820f335d52653b16d4843b29dd9cdcf38ae80406756db9199497cf3 - languageName: node - linkType: hard - -"keygrip@npm:~1.1.0": - version: 1.1.0 - resolution: "keygrip@npm:1.1.0" - dependencies: - tsscmp: "npm:1.0.6" - checksum: 10/078cd16a463d187121f0a27c1c9c95c52ad392b620f823431689f345a0501132cee60f6e96914b07d570105af470b96960402accd6c48a0b1f3cd8fac4fa2cae - languageName: node - linkType: hard - -"kleur@npm:^3.0.3": - version: 3.0.3 - resolution: "kleur@npm:3.0.3" - checksum: 10/0c0ecaf00a5c6173d25059c7db2113850b5457016dfa1d0e3ef26da4704fbb186b4938d7611246d86f0ddf1bccf26828daa5877b1f232a65e7373d0122a83e7f - languageName: node - linkType: hard - -"koa-body@npm:^6.0.1": - version: 6.0.1 - resolution: "koa-body@npm:6.0.1" - dependencies: - "@types/co-body": "npm:^6.1.0" - "@types/formidable": "npm:^2.0.5" - "@types/koa": "npm:^2.13.5" - co-body: "npm:^6.1.0" - formidable: "npm:^2.0.1" - zod: "npm:^3.19.1" - checksum: 10/d241d4d228117da43ccd485babe9f8e221188360faef93f936f85ced03d8df900b1bd3af0f2e26b8e514f66361373078ef8501b50089b20e19c578566d25a239 - languageName: node - linkType: hard - -"koa-compose@npm:^4.1.0": - version: 4.1.0 - resolution: "koa-compose@npm:4.1.0" - checksum: 10/46cb16792d96425e977c2ae4e5cb04930280740e907242ec9c25e3fb8b4a1d7b54451d7432bc24f40ec62255edea71894d2ceeb8238501842b4e48014f2e83db - languageName: node - linkType: hard - -"koa@npm:^3.0.3": - version: 3.1.1 - resolution: "koa@npm:3.1.1" - dependencies: - accepts: "npm:^1.3.8" - content-disposition: "npm:~0.5.4" - content-type: "npm:^1.0.5" - cookies: "npm:~0.9.1" - delegates: "npm:^1.0.0" - destroy: "npm:^1.2.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - fresh: "npm:~0.5.2" - http-assert: "npm:^1.5.0" - http-errors: "npm:^2.0.0" - koa-compose: "npm:^4.1.0" - mime-types: "npm:^3.0.1" - on-finished: "npm:^2.4.1" - parseurl: "npm:^1.3.3" - statuses: "npm:^2.0.1" - type-is: "npm:^2.0.1" - vary: "npm:^1.1.2" - checksum: 10/b9f53e98752e73d2d3ed2df28a8062387e116d7053f3d655815ea7f1bae672f4f6afe41d6ff5f6cf429aad76aea4fd4424655bdcf6f8291a658108fe3aa2cf43 - languageName: node - linkType: hard - -"linkify-it@npm:^5.0.0": - version: 5.0.0 - resolution: "linkify-it@npm:5.0.0" - dependencies: - uc.micro: "npm:^2.0.0" - checksum: 10/ef3b7609dda6ec0c0be8a7b879cea195f0d36387b0011660cd6711bba0ad82137f59b458b7e703ec74f11d88e7c1328e2ad9b855a8500c0ded67461a8c4519e6 - languageName: node - linkType: hard - -"lodash@npm:^3.3.1": - version: 3.10.1 - resolution: "lodash@npm:3.10.1" - checksum: 10/3fa24526ced39885889d5609239d14c4145b3736f103fcdde247524203bfeb38e641653df7bdcc172b316a9775ca32787dda09430872ce906f017a699c92fad8 - languageName: node - linkType: hard - -"lodash@npm:^4.5.1": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 - languageName: node - linkType: hard - -"log-update@npm:^1.0.2": - version: 1.0.2 - resolution: "log-update@npm:1.0.2" - dependencies: - ansi-escapes: "npm:^1.0.0" - cli-cursor: "npm:^1.0.2" - checksum: 10/eb8389778092093ec65f36f6a81dd599d0196b74176f07668fcf2bbeb805e36548b438655060e14dcfb910c47f2ef2ff9984c50be9aabeaa772d8aa448a374aa - languageName: node - linkType: hard - -"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a - languageName: node - linkType: hard - -"lunr@npm:^2.3.9": - version: 2.3.9 - resolution: "lunr@npm:2.3.9" - checksum: 10/f2f6db34c046f5a767782fe2454e6dd69c75ba3c5cf5c1cb9cacca2313a99c2ba78ff8fa67dac866fb7c4ffd5f22e06684793f5f15ba14bddb598b94513d54bf - languageName: node - linkType: hard - -"make-error@npm:^1.1.1, make-error@npm:^1.3.6": - version: 1.3.6 - resolution: "make-error@npm:1.3.6" - checksum: 10/b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 - languageName: node - linkType: hard - -"make-fetch-happen@npm:^14.0.3": - version: 14.0.3 - resolution: "make-fetch-happen@npm:14.0.3" - dependencies: - "@npmcli/agent": "npm:^3.0.0" - cacache: "npm:^19.0.1" - http-cache-semantics: "npm:^4.1.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^4.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^1.0.0" - proc-log: "npm:^5.0.0" - promise-retry: "npm:^2.0.1" - ssri: "npm:^12.0.0" - checksum: 10/fce0385840b6d86b735053dfe941edc2dd6468fda80fe74da1eeff10cbd82a75760f406194f2bc2fa85b99545b2bc1f84c08ddf994b21830775ba2d1a87e8bdf - languageName: node - linkType: hard - -"markdown-it@npm:^14.1.0": - version: 14.1.0 - resolution: "markdown-it@npm:14.1.0" - dependencies: - argparse: "npm:^2.0.1" - entities: "npm:^4.4.0" - linkify-it: "npm:^5.0.0" - mdurl: "npm:^2.0.0" - punycode.js: "npm:^2.3.1" - uc.micro: "npm:^2.1.0" - bin: - markdown-it: bin/markdown-it.mjs - checksum: 10/f34f921be178ed0607ba9e3e27c733642be445e9bb6b1dba88da7aafe8ba1bc5d2f1c3aa8f3fc33b49a902da4e4c08c2feadfafb290b8c7dda766208bb6483a9 - languageName: node - linkType: hard - -"math-intrinsics@npm:^1.1.0": - version: 1.1.0 - resolution: "math-intrinsics@npm:1.1.0" - checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd - languageName: node - linkType: hard - -"mdurl@npm:^2.0.0": - version: 2.0.0 - resolution: "mdurl@npm:2.0.0" - checksum: 10/1720349d4a53e401aa993241368e35c0ad13d816ad0b28388928c58ca9faa0cf755fa45f18ccbf64f4ce54a845a50ddce5c84e4016897b513096a68dac4b0158 - languageName: node - linkType: hard - -"media-typer@npm:0.3.0": - version: 0.3.0 - resolution: "media-typer@npm:0.3.0" - checksum: 10/38e0984db39139604756903a01397e29e17dcb04207bb3e081412ce725ab17338ecc47220c1b186b6bbe79a658aad1b0d41142884f5a481f36290cdefbe6aa46 - languageName: node - linkType: hard - -"media-typer@npm:^1.1.0": - version: 1.1.0 - resolution: "media-typer@npm:1.1.0" - checksum: 10/a58dd60804df73c672942a7253ccc06815612326dc1c0827984b1a21704466d7cde351394f47649e56cf7415e6ee2e26e000e81b51b3eebb5a93540e8bf93cbd - languageName: node - linkType: hard - -"mime-db@npm:1.52.0": - version: 1.52.0 - resolution: "mime-db@npm:1.52.0" - checksum: 10/54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 - languageName: node - linkType: hard - -"mime-db@npm:^1.54.0": - version: 1.54.0 - resolution: "mime-db@npm:1.54.0" - checksum: 10/9e7834be3d66ae7f10eaa69215732c6d389692b194f876198dca79b2b90cbf96688d9d5d05ef7987b20f749b769b11c01766564264ea5f919c88b32a29011311 - languageName: node - linkType: hard - -"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": - version: 3.0.1 - resolution: "mime-types@npm:3.0.1" - dependencies: - mime-db: "npm:^1.54.0" - checksum: 10/fa1d3a928363723a8046c346d87bf85d35014dae4285ad70a3ff92bd35957992b3094f8417973cfe677330916c6ef30885109624f1fb3b1e61a78af509dba120 - languageName: node - linkType: hard - -"mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10/89aa9651b67644035de2784a6e665fc685d79aba61857e02b9c8758da874a754aed4a9aced9265f5ed1171fd934331e5516b84a7f0218031b6fa0270eca1e51a - languageName: node - linkType: hard - -"minimatch@npm:^3.0.4": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10/e0b25b04cd4ec6732830344e5739b13f8690f8a012d73445a4a19fbc623f5dd481ef7a5827fde25954cd6026fede7574cc54dc4643c99d6c6b653d6203f94634 - languageName: node - linkType: hard - -"minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348 - languageName: node - linkType: hard - -"minimist@npm:^1.2.0": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f - languageName: node - linkType: hard - -"minipass-collect@npm:^2.0.1": - version: 2.0.1 - resolution: "minipass-collect@npm:2.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10/b251bceea62090f67a6cced7a446a36f4cd61ee2d5cea9aee7fff79ba8030e416327a1c5aa2908dc22629d06214b46d88fdab8c51ac76bacbf5703851b5ad342 - languageName: node - linkType: hard - -"minipass-fetch@npm:^4.0.0": - version: 4.0.1 - resolution: "minipass-fetch@npm:4.0.1" - dependencies: - encoding: "npm:^0.1.13" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^3.0.1" - dependenciesMeta: - encoding: - optional: true - checksum: 10/7ddfebdbb87d9866e7b5f7eead5a9e3d9d507992af932a11d275551f60006cf7d9178e66d586dbb910894f3e3458d27c0ddf93c76e94d49d0a54a541ddc1263d - languageName: node - linkType: hard - -"minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10/56269a0b22bad756a08a94b1ffc36b7c9c5de0735a4dd1ab2b06c066d795cfd1f0ac44a0fcae13eece5589b908ecddc867f04c745c7009be0b566421ea0944cf - languageName: node - linkType: hard - -"minipass-pipeline@npm:^1.2.4": - version: 1.2.4 - resolution: "minipass-pipeline@npm:1.2.4" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10/b14240dac0d29823c3d5911c286069e36d0b81173d7bdf07a7e4a91ecdef92cdff4baaf31ea3746f1c61e0957f652e641223970870e2353593f382112257971b - languageName: node - linkType: hard - -"minipass-sized@npm:^1.0.3": - version: 1.0.3 - resolution: "minipass-sized@npm:1.0.3" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10/40982d8d836a52b0f37049a0a7e5d0f089637298e6d9b45df9c115d4f0520682a78258905e5c8b180fb41b593b0a82cc1361d2c74b45f7ada66334f84d1ecfdd - languageName: node - linkType: hard - -"minipass@npm:^3.0.0": - version: 3.3.6 - resolution: "minipass@npm:3.3.6" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10/a5c6ef069f70d9a524d3428af39f2b117ff8cd84172e19b754e7264a33df460873e6eb3d6e55758531580970de50ae950c496256bb4ad3691a2974cddff189f0 - languageName: node - linkType: hard - -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 - languageName: node - linkType: hard - -"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": - version: 3.1.0 - resolution: "minizlib@npm:3.1.0" - dependencies: - minipass: "npm:^7.1.2" - checksum: 10/f47365cc2cb7f078cbe7e046eb52655e2e7e97f8c0a9a674f4da60d94fb0624edfcec9b5db32e8ba5a99a5f036f595680ae6fe02a262beaa73026e505cc52f99 - languageName: node - linkType: hard - -"ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d - languageName: node - linkType: hard - -"mute-stream@npm:0.0.5": - version: 0.0.5 - resolution: "mute-stream@npm:0.0.5" - checksum: 10/a2a3b25fa0e6adf3516f51b1d8bd0cc48e256fc9384c3c5bd3a5c3635e0823d6dee4c28909a3f2a9d8782d7d8daa3a8c081163d881e4d7f30ff17e7d2eabde76 - languageName: node - linkType: hard - -"negotiator@npm:0.6.3": - version: 0.6.3 - resolution: "negotiator@npm:0.6.3" - checksum: 10/2723fb822a17ad55c93a588a4bc44d53b22855bf4be5499916ca0cab1e7165409d0b288ba2577d7b029f10ce18cf2ed8e703e5af31c984e1e2304277ef979837 - languageName: node - linkType: hard - -"negotiator@npm:^1.0.0": - version: 1.0.0 - resolution: "negotiator@npm:1.0.0" - checksum: 10/b5734e87295324fabf868e36fb97c84b7d7f3156ec5f4ee5bf6e488079c11054f818290fc33804cef7b1ee21f55eeb14caea83e7dafae6492a409b3e573153e5 - languageName: node - linkType: hard - -"node-fetch@npm:^2.6.1": - version: 2.7.0 - resolution: "node-fetch@npm:2.7.0" - dependencies: - whatwg-url: "npm:^5.0.0" - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 10/b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676 - languageName: node - linkType: hard - -"node-gyp@npm:latest": - version: 11.5.0 - resolution: "node-gyp@npm:11.5.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^14.0.3" - nopt: "npm:^8.0.0" - proc-log: "npm:^5.0.0" - semver: "npm:^7.3.5" - tar: "npm:^7.4.3" - tinyglobby: "npm:^0.2.12" - which: "npm:^5.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10/15a600b626116e1e528c49f73027c5ff84dbf6986df77b0fb61d6eb079ab4230c39f245295cb67f0590e6541a848cbd267e00c5769e8fb8bf88a5cca3701b551 - languageName: node - linkType: hard - -"node-localstorage@npm:^0.6.0": - version: 0.6.0 - resolution: "node-localstorage@npm:0.6.0" - checksum: 10/9bc98d8ddcdf651c25ab35f730c133b13d03accd3334563845281aa1287254f6ca862ec56a337ea740cb5458fc7791aaeee8004aabf80dffd112dd8e1270b4a0 - languageName: node - linkType: hard - -"nopt@npm:^8.0.0": - version: 8.1.0 - resolution: "nopt@npm:8.1.0" - dependencies: - abbrev: "npm:^3.0.0" - bin: - nopt: bin/nopt.js - checksum: 10/26ab456c51a96f02a9e5aa8d1b80ef3219f2070f3f3528a040e32fb735b1e651e17bdf0f1476988d3a46d498f35c65ed662d122f340d38ce4a7e71dd7b20c4bc - languageName: node - linkType: hard - -"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": - version: 3.0.0 - resolution: "normalize-path@npm:3.0.0" - checksum: 10/88eeb4da891e10b1318c4b2476b6e2ecbeb5ff97d946815ffea7794c31a89017c70d7f34b3c2ebf23ef4e9fc9fb99f7dffe36da22011b5b5c6ffa34f4873ec20 - languageName: node - linkType: hard - -"number-is-nan@npm:^1.0.0": - version: 1.0.1 - resolution: "number-is-nan@npm:1.0.1" - checksum: 10/13656bc9aa771b96cef209ffca31c31a03b507ca6862ba7c3f638a283560620d723d52e626d57892c7fff475f4c36ac07f0600f14544692ff595abff214b9ffb - languageName: node - linkType: hard - -"object-assign@npm:^4.1.0": - version: 4.1.1 - resolution: "object-assign@npm:4.1.1" - checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f - languageName: node - linkType: hard - -"object-inspect@npm:^1.13.3": - version: 1.13.4 - resolution: "object-inspect@npm:1.13.4" - checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb - languageName: node - linkType: hard - -"on-finished@npm:^2.4.1": - version: 2.4.1 - resolution: "on-finished@npm:2.4.1" - dependencies: - ee-first: "npm:1.1.1" - checksum: 10/8e81472c5028125c8c39044ac4ab8ba51a7cdc19a9fbd4710f5d524a74c6d8c9ded4dd0eed83f28d3d33ac1d7a6a439ba948ccb765ac6ce87f30450a26bfe2ea - languageName: node - linkType: hard - -"once@npm:^1.3.0, once@npm:^1.4.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: "npm:1" - checksum: 10/cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68 - languageName: node - linkType: hard - -"onetime@npm:^1.0.0": - version: 1.1.0 - resolution: "onetime@npm:1.1.0" - checksum: 10/751f45ddeba213600c215280cb937efd7b530b498277c9605f9ee0e5e2022b3463e23fd6d1800e793e128301adee12ed4aba41242e50fbc0d631a9e171aef361 - languageName: node - linkType: hard - -"p-map@npm:^7.0.2": - version: 7.0.3 - resolution: "p-map@npm:7.0.3" - checksum: 10/2ef48ccfc6dd387253d71bf502604f7893ed62090b2c9d73387f10006c342606b05233da0e4f29388227b61eb5aeface6197e166520c465c234552eeab2fe633 - languageName: node - linkType: hard - -"package-json-from-dist@npm:^1.0.0": - version: 1.0.1 - resolution: "package-json-from-dist@npm:1.0.1" - checksum: 10/58ee9538f2f762988433da00e26acc788036914d57c71c246bf0be1b60cdbd77dd60b6a3e1a30465f0b248aeb80079e0b34cb6050b1dfa18c06953bb1cbc7602 - languageName: node - linkType: hard - -"parseurl@npm:^1.3.3": - version: 1.3.3 - resolution: "parseurl@npm:1.3.3" - checksum: 10/407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 - languageName: node - linkType: hard - -"path-key@npm:^3.1.0": - version: 3.1.1 - resolution: "path-key@npm:3.1.1" - checksum: 10/55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 - languageName: node - linkType: hard - -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10/5e8845c159261adda6f09814d7725683257fcc85a18f329880ab4d7cc1d12830967eae5d5894e453f341710d5484b8fdbbd4d75181b4d6e1eb2f4dc7aeadc434 - languageName: node - linkType: hard - -"path-to-regexp@npm:^8.2.0": - version: 8.3.0 - resolution: "path-to-regexp@npm:8.3.0" - checksum: 10/568f148fc64f5fd1ecebf44d531383b28df924214eabf5f2570dce9587a228e36c37882805ff02d71c6209b080ea3ee6a4d2b712b5df09741b67f1f3cf91e55a - languageName: node - linkType: hard - -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc - languageName: node - linkType: hard - -"picomatch@npm:^4.0.3": - version: 4.0.3 - resolution: "picomatch@npm:4.0.3" - checksum: 10/57b99055f40b16798f2802916d9c17e9744e620a0db136554af01d19598b96e45e2f00014c91d1b8b13874b80caa8c295b3d589a3f72373ec4aaf54baa5962d5 - languageName: node - linkType: hard - -"pinst@npm:^2.1.6": - version: 2.1.6 - resolution: "pinst@npm:2.1.6" - dependencies: - fromentries: "npm:^1.3.2" - bin: - pinst: bin.js - checksum: 10/b6449d527ec6c7b76056a5282a80ff4d2498d4e882e846eb8837ec291ad1b08784756c6d9de0f1d96b70bd657e50b52db3ae2a683c6245690e12c2c7eb199d0f - languageName: node - linkType: hard - -"proc-log@npm:^5.0.0": - version: 5.0.0 - resolution: "proc-log@npm:5.0.0" - checksum: 10/35610bdb0177d3ab5d35f8827a429fb1dc2518d9e639f2151ac9007f01a061c30e0c635a970c9b00c39102216160f6ec54b62377c92fac3b7bfc2ad4b98d195c - languageName: node - linkType: hard - -"promise-retry@npm:^2.0.1": - version: 2.0.1 - resolution: "promise-retry@npm:2.0.1" - dependencies: - err-code: "npm:^2.0.2" - retry: "npm:^0.12.0" - checksum: 10/96e1a82453c6c96eef53a37a1d6134c9f2482f94068f98a59145d0986ca4e497bf110a410adf73857e588165eab3899f0ebcf7b3890c1b3ce802abc0d65967d4 - languageName: node - linkType: hard - -"prompts@npm:^2.4.2": - version: 2.4.2 - resolution: "prompts@npm:2.4.2" - dependencies: - kleur: "npm:^3.0.3" - sisteransi: "npm:^1.0.5" - checksum: 10/c52536521a4d21eff4f2f2aa4572446cad227464066365a7167e52ccf8d9839c099f9afec1aba0eed3d5a2514b3e79e0b3e7a1dc326b9acde6b75d27ed74b1a9 - languageName: node - linkType: hard - -"punycode.js@npm:^2.3.1": - version: 2.3.1 - resolution: "punycode.js@npm:2.3.1" - checksum: 10/f0e946d1edf063f9e3d30a32ca86d8ff90ed13ca40dad9c75d37510a04473340cfc98db23a905cc1e517b1e9deb0f6021dce6f422ace235c60d3c9ac47c5a16a - languageName: node - linkType: hard - -"qs@npm:^6.11.0, qs@npm:^6.5.2": - version: 6.14.0 - resolution: "qs@npm:6.14.0" - dependencies: - side-channel: "npm:^1.1.0" - checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 - languageName: node - linkType: hard - -"query-registry@npm:^2.5.0": - version: 2.6.0 - resolution: "query-registry@npm:2.6.0" - dependencies: - isomorphic-unfetch: "npm:^3.1.0" - make-error: "npm:^1.3.6" - tiny-lru: "npm:^8.0.2" - url-join: "npm:4.0.1" - validate-npm-package-name: "npm:^4.0.0" - checksum: 10/2d12a4f82339946c61bc57977c8669d344affd824ba426f1bb1800458c0c6a7c4722385f4dbb42c54bc3e3e1e09dc4cbb0bfe41a59d8c308963fcc1dafee6280 - languageName: node - linkType: hard - -"raw-body@npm:^2.3.3": - version: 2.5.2 - resolution: "raw-body@npm:2.5.2" - dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - unpipe: "npm:1.0.0" - checksum: 10/863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95 - languageName: node - linkType: hard - -"readdirp@npm:~3.6.0": - version: 3.6.0 - resolution: "readdirp@npm:3.6.0" - dependencies: - picomatch: "npm:^2.2.1" - checksum: 10/196b30ef6ccf9b6e18c4e1724b7334f72a093d011a99f3b5920470f0b3406a51770867b3e1ae9711f227ef7a7065982f6ee2ce316746b2cb42c88efe44297fe7 - languageName: node - linkType: hard - -"readline2@npm:^1.0.1": - version: 1.0.1 - resolution: "readline2@npm:1.0.1" - dependencies: - code-point-at: "npm:^1.0.0" - is-fullwidth-code-point: "npm:^1.0.0" - mute-stream: "npm:0.0.5" - checksum: 10/7ac8ffa917af89f042bb24f695b1333158d83e26f398108f6d4ce7ca3ab6bccb6fa32623d9254ea1dc5420db7e6ce0b0fc527108645ababf6e280d8db3fe8a89 - languageName: node - linkType: hard - -"reflect-metadata@npm:^0.1.13": - version: 0.1.14 - resolution: "reflect-metadata@npm:0.1.14" - checksum: 10/fcab9c17ec3b9fea0e2f748c2129aceb57c24af6d8d13842b8a77c8c79dde727d7456ce293e76e8d7b267d1dbf93eea4c5b3c9101299a789a075824f2e40f1ee - languageName: node - linkType: hard - -"regenerator-runtime@npm:^0.10.5": - version: 0.10.5 - resolution: "regenerator-runtime@npm:0.10.5" - checksum: 10/a10d9a2510ee0ec2603f2fc316bff0233b7f41702dc69a19b6a23442395a7be9247668f06e5b7a81577d0e3ef677a11c8c63b4edd7a16f1550e5b8fb22173346 - languageName: node - linkType: hard - -"regenerator-runtime@npm:^0.11.0": - version: 0.11.1 - resolution: "regenerator-runtime@npm:0.11.1" - checksum: 10/64e62d78594c227e7d5269811bca9e4aa6451332adaae8c79a30cab0fa98733b1ad90bdb9d038095c340c6fad3b414a49a8d9e0b6b424ab7ff8f94f35704f8a2 - languageName: node - linkType: hard - -"resolve-from@npm:^5.0.0": - version: 5.0.0 - resolution: "resolve-from@npm:5.0.0" - checksum: 10/be18a5e4d76dd711778664829841cde690971d02b6cbae277735a09c1c28f407b99ef6ef3cd585a1e6546d4097b28df40ed32c4a287b9699dcf6d7f208495e23 - languageName: node - linkType: hard - -"resolve-pkg@npm:^2.0.0": - version: 2.0.0 - resolution: "resolve-pkg@npm:2.0.0" - dependencies: - resolve-from: "npm:^5.0.0" - checksum: 10/4a14cc38effed20ff362c8f377719af9a45ebe27ee07d79d4802b4568858cd96033f4edc3a2add7fd27e361d24101a042047297a9ef9476696ba16b72e0a05fc - languageName: node - linkType: hard - -"restore-cursor@npm:^1.0.1": - version: 1.0.1 - resolution: "restore-cursor@npm:1.0.1" - dependencies: - exit-hook: "npm:^1.0.0" - onetime: "npm:^1.0.0" - checksum: 10/e40bd1a540d69970341fc734dfada908815a44f91903211f34d32c47da33f6e7824bbc97f6e76aff387137d6b2a1ada3d3d2dc1b654b8accdc8ed5721c46cbfa - languageName: node - linkType: hard - -"retry@npm:^0.12.0": - version: 0.12.0 - resolution: "retry@npm:0.12.0" - checksum: 10/1f914879f97e7ee931ad05fe3afa629bd55270fc6cf1c1e589b6a99fab96d15daad0fa1a52a00c729ec0078045fe3e399bd4fd0c93bcc906957bdc17f89cb8e6 - languageName: node - linkType: hard - -"run-async@npm:^0.1.0": - version: 0.1.0 - resolution: "run-async@npm:0.1.0" - dependencies: - once: "npm:^1.3.0" - checksum: 10/66fd3ada4036a77a70fbf5063d66bf88df77fa9cbf20516115a6a09431ba66621f353e6fefecd10f9cb6a3345b5fe007a438dbf3f6020fbfd5732634cd4d3e15 - languageName: node - linkType: hard - -"rx-lite@npm:^3.1.2": - version: 3.1.2 - resolution: "rx-lite@npm:3.1.2" - checksum: 10/e11d3b1a044e0fe5af82b923dee68aa83b193bf3ad8128cf70e033cbc414f175011a644419c25fe62f75a6f20a1f2aee3b407847dae129fa8df1198b618fb1b2 - languageName: node - linkType: hard - -"safe-buffer@npm:5.2.1": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: 10/7eaf7a0cf37cc27b42fb3ef6a9b1df6e93a1c6d98c6c6702b02fe262d5fcbd89db63320793b99b21cb5348097d0a53de81bd5f4e8b86e20cc9412e3f1cfb4e83 - languageName: node - linkType: hard - -"semver@npm:^7.0.0, semver@npm:^7.3.5": - version: 7.7.3 - resolution: "semver@npm:7.7.3" - bin: - semver: bin/semver.js - checksum: 10/8dbc3168e057a38fc322af909c7f5617483c50caddba135439ff09a754b20bdd6482a5123ff543dad4affa488ecf46ec5fb56d61312ad20bb140199b88dfaea9 - languageName: node - linkType: hard - -"setprototypeof@npm:1.2.0": - version: 1.2.0 - resolution: "setprototypeof@npm:1.2.0" - checksum: 10/fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e - languageName: node - linkType: hard - -"shebang-command@npm:^2.0.0": - version: 2.0.0 - resolution: "shebang-command@npm:2.0.0" - dependencies: - shebang-regex: "npm:^3.0.0" - checksum: 10/6b52fe87271c12968f6a054e60f6bde5f0f3d2db483a1e5c3e12d657c488a15474121a1d55cd958f6df026a54374ec38a4a963988c213b7570e1d51575cea7fa - languageName: node - linkType: hard - -"shebang-regex@npm:^3.0.0": - version: 3.0.0 - resolution: "shebang-regex@npm:3.0.0" - checksum: 10/1a2bcae50de99034fcd92ad4212d8e01eedf52c7ec7830eedcf886622804fe36884278f2be8be0ea5fde3fd1c23911643a4e0f726c8685b61871c8908af01222 - languageName: node - linkType: hard - -"side-channel-list@npm:^1.0.0": - version: 1.0.0 - resolution: "side-channel-list@npm:1.0.0" - dependencies: - es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - checksum: 10/603b928997abd21c5a5f02ae6b9cc36b72e3176ad6827fab0417ead74580cc4fb4d5c7d0a8a2ff4ead34d0f9e35701ed7a41853dac8a6d1a664fcce1a044f86f - languageName: node - linkType: hard - -"side-channel-map@npm:^1.0.1": - version: 1.0.1 - resolution: "side-channel-map@npm:1.0.1" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.5" - object-inspect: "npm:^1.13.3" - checksum: 10/5771861f77feefe44f6195ed077a9e4f389acc188f895f570d56445e251b861754b547ea9ef73ecee4e01fdada6568bfe9020d2ec2dfc5571e9fa1bbc4a10615 - languageName: node - linkType: hard - -"side-channel-weakmap@npm:^1.0.2": - version: 1.0.2 - resolution: "side-channel-weakmap@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.5" - object-inspect: "npm:^1.13.3" - side-channel-map: "npm:^1.0.1" - checksum: 10/a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736 - languageName: node - linkType: hard - -"side-channel@npm:^1.1.0": - version: 1.1.0 - resolution: "side-channel@npm:1.1.0" - dependencies: - es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - side-channel-list: "npm:^1.0.0" - side-channel-map: "npm:^1.0.1" - side-channel-weakmap: "npm:^1.0.2" - checksum: 10/7d53b9db292c6262f326b6ff3bc1611db84ece36c2c7dc0e937954c13c73185b0406c56589e2bb8d071d6fee468e14c39fb5d203ee39be66b7b8174f179afaba - languageName: node - linkType: hard - -"signal-exit@npm:^4.0.1": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f - languageName: node - linkType: hard - -"sisteransi@npm:^1.0.5": - version: 1.0.5 - resolution: "sisteransi@npm:1.0.5" - checksum: 10/aba6438f46d2bfcef94cf112c835ab395172c75f67453fe05c340c770d3c402363018ae1ab4172a1026a90c47eaccf3af7b6ff6fa749a680c2929bd7fa2b37a4 - languageName: node - linkType: hard - -"smart-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "smart-buffer@npm:4.2.0" - checksum: 10/927484aa0b1640fd9473cee3e0a0bcad6fce93fd7bbc18bac9ad0c33686f5d2e2c422fba24b5899c184524af01e11dd2bd051c2bf2b07e47aff8ca72cbfc60d2 - languageName: node - linkType: hard - -"socks-proxy-agent@npm:^8.0.3": - version: 8.0.5 - resolution: "socks-proxy-agent@npm:8.0.5" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:^4.3.4" - socks: "npm:^2.8.3" - checksum: 10/ee99e1dacab0985b52cbe5a75640be6e604135e9489ebdc3048635d186012fbaecc20fbbe04b177dee434c319ba20f09b3e7dfefb7d932466c0d707744eac05c - languageName: node - linkType: hard - -"socks@npm:^2.8.3": - version: 2.8.7 - resolution: "socks@npm:2.8.7" - dependencies: - ip-address: "npm:^10.0.1" - smart-buffer: "npm:^4.2.0" - checksum: 10/d19366c95908c19db154f329bbe94c2317d315dc933a7c2b5101e73f32a555c84fb199b62174e1490082a593a4933d8d5a9b297bde7d1419c14a11a965f51356 - languageName: node - linkType: hard - -"ssri@npm:^12.0.0": - version: 12.0.0 - resolution: "ssri@npm:12.0.0" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10/7024c1a6e39b3f18aa8f1c8290e884fe91b0f9ca5a6c6d410544daad54de0ba664db879afe16412e187c6c292fd60b937f047ee44292e5c2af2dcc6d8e1a9b48 - languageName: node - linkType: hard - -"statuses@npm:2.0.1": - version: 2.0.1 - resolution: "statuses@npm:2.0.1" - checksum: 10/18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb - languageName: node - linkType: hard - -"statuses@npm:>= 1.5.0 < 2": - version: 1.5.0 - resolution: "statuses@npm:1.5.0" - checksum: 10/c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c - languageName: node - linkType: hard - -"statuses@npm:^2.0.1": - version: 2.0.2 - resolution: "statuses@npm:2.0.2" - checksum: 10/6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879 - languageName: node - linkType: hard - -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10/e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb - languageName: node - linkType: hard - -"string-width@npm:^1.0.1": - version: 1.0.2 - resolution: "string-width@npm:1.0.2" - dependencies: - code-point-at: "npm:^1.0.0" - is-fullwidth-code-point: "npm:^1.0.0" - strip-ansi: "npm:^3.0.0" - checksum: 10/5c79439e95bc3bd7233a332c5f5926ab2ee90b23816ed4faa380ce3b2576d7800b0a5bb15ae88ed28737acc7ea06a518c2eef39142dd727adad0e45c776cd37e - languageName: node - linkType: hard - -"string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10/7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 - languageName: node - linkType: hard - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" - dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10/ae3b5436d34fadeb6096367626ce987057713c566e1e7768818797e00ac5d62023d0f198c4e681eae9e20701721980b26a64a8f5b91238869592a9c6800719a2 - languageName: node - linkType: hard - -"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": - version: 3.0.1 - resolution: "strip-ansi@npm:3.0.1" - dependencies: - ansi-regex: "npm:^2.0.0" - checksum: 10/9b974de611ce5075c70629c00fa98c46144043db92ae17748fb780f706f7a789e9989fd10597b7c2053ae8d1513fd707816a91f1879b2f71e6ac0b6a863db465 - languageName: node - linkType: hard - -"strip-ansi@npm:^7.0.1": - version: 7.1.2 - resolution: "strip-ansi@npm:7.1.2" - dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10/db0e3f9654e519c8a33c50fc9304d07df5649388e7da06d3aabf66d29e5ad65d5e6315d8519d409c15b32fa82c1df7e11ed6f8cd50b0e4404463f0c9d77c8d0b - languageName: node - linkType: hard - -"supports-color@npm:^2.0.0": - version: 2.0.0 - resolution: "supports-color@npm:2.0.0" - checksum: 10/d2957d19e782a806abc3e8616b6648cc1e70c3ebe94fb1c2d43160686f6d79cd7c9f22c4853bc4a362d89d1c249ab6d429788c5f6c83b3086e6d763024bf4581 - languageName: node - linkType: hard - -"supports-color@npm:^7.1.0": - version: 7.2.0 - resolution: "supports-color@npm:7.2.0" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10/c8bb7afd564e3b26b50ca6ee47572c217526a1389fe018d00345856d4a9b08ffbd61fadaf283a87368d94c3dcdb8f5ffe2650a5a65863e21ad2730ca0f05210a - languageName: node - linkType: hard - -"tar@npm:^7.4.3": - version: 7.5.1 - resolution: "tar@npm:7.5.1" - dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.1.0" - yallist: "npm:^5.0.0" - checksum: 10/4848cd2fa2fcaf0734cf54e14bc685056eb43a74d7cc7f954c3ac88fea88c85d95b1d7896619f91aab6f2234c5eec731c18aaa201a78fcf86985bdc824ed7a00 - languageName: node - linkType: hard - -"through@npm:^2.3.6": - version: 2.3.8 - resolution: "through@npm:2.3.8" - checksum: 10/5da78346f70139a7d213b65a0106f3c398d6bc5301f9248b5275f420abc2c4b1e77c2abc72d218dedc28c41efb2e7c312cb76a7730d04f9c2d37d247da3f4198 - languageName: node - linkType: hard - -"tiny-lru@npm:^8.0.2": - version: 8.0.2 - resolution: "tiny-lru@npm:8.0.2" - checksum: 10/74b193d83b9edbe690b4e97e3cfd66c8a66d593c33b2c7133f132adacbf0442afbef82c679848208010137175aac3db9e68f350bef68cc299c0f2eb589554ffb - languageName: node - linkType: hard - -"tinyglobby@npm:^0.2.12": - version: 0.2.15 - resolution: "tinyglobby@npm:0.2.15" - dependencies: - fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.3" - checksum: 10/d72bd826a8b0fa5fa3929e7fe5ba48fceb2ae495df3a231b6c5408cd7d8c00b58ab5a9c2a76ba56a62ee9b5e083626f1f33599734bed1ffc4b792406408f0ca2 - languageName: node - linkType: hard - -"to-regex-range@npm:^5.0.1": - version: 5.0.1 - resolution: "to-regex-range@npm:5.0.1" - dependencies: - is-number: "npm:^7.0.0" - checksum: 10/10dda13571e1f5ad37546827e9b6d4252d2e0bc176c24a101252153ef435d83696e2557fe128c4678e4e78f5f01e83711c703eef9814eb12dab028580d45980a - languageName: node - linkType: hard - -"toidentifier@npm:1.0.1": - version: 1.0.1 - resolution: "toidentifier@npm:1.0.1" - checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 - languageName: node - linkType: hard - -"tr46@npm:~0.0.3": - version: 0.0.3 - resolution: "tr46@npm:0.0.3" - checksum: 10/8f1f5aa6cb232f9e1bdc86f485f916b7aa38caee8a778b378ffec0b70d9307873f253f5cbadbe2955ece2ac5c83d0dc14a77513166ccd0a0c7fe197e21396695 - languageName: node - linkType: hard - -"triple-beam@npm:^1.3.0": - version: 1.4.1 - resolution: "triple-beam@npm:1.4.1" - checksum: 10/2e881a3e8e076b6f2b85b9ec9dd4a900d3f5016e6d21183ed98e78f9abcc0149e7d54d79a3f432b23afde46b0885bdcdcbff789f39bc75de796316961ec07f61 - languageName: node - linkType: hard - -"ts-node@npm:^10.0.0": - version: 10.9.2 - resolution: "ts-node@npm:10.9.2" - dependencies: - "@cspotcode/source-map-support": "npm:^0.8.0" - "@tsconfig/node10": "npm:^1.0.7" - "@tsconfig/node12": "npm:^1.0.7" - "@tsconfig/node14": "npm:^1.0.0" - "@tsconfig/node16": "npm:^1.0.2" - acorn: "npm:^8.4.1" - acorn-walk: "npm:^8.1.1" - arg: "npm:^4.1.0" - create-require: "npm:^1.1.0" - diff: "npm:^4.0.1" - make-error: "npm:^1.1.1" - v8-compile-cache-lib: "npm:^3.0.1" - yn: "npm:3.1.1" - peerDependencies: - "@swc/core": ">=1.2.50" - "@swc/wasm": ">=1.2.50" - "@types/node": "*" - typescript: ">=2.7" - peerDependenciesMeta: - "@swc/core": - optional: true - "@swc/wasm": - optional: true - bin: - ts-node: dist/bin.js - ts-node-cwd: dist/bin-cwd.js - ts-node-esm: dist/bin-esm.js - ts-node-script: dist/bin-script.js - ts-node-transpile-only: dist/bin-transpile.js - ts-script: dist/bin-script-deprecated.js - checksum: 10/a91a15b3c9f76ac462f006fa88b6bfa528130dcfb849dd7ef7f9d640832ab681e235b8a2bc58ecde42f72851cc1d5d4e22c901b0c11aa51001ea1d395074b794 - languageName: node - linkType: hard - -"tsscmp@npm:1.0.6": - version: 1.0.6 - resolution: "tsscmp@npm:1.0.6" - checksum: 10/850405080ea3ecb158e9e01bc4e87c9edb94a829d8ad8747f30ba103fcc41a287d7949ab84d7b27c36294036a2c9878f050db15b73a1a1961abfb7688b82ac53 - languageName: node - linkType: hard - -"type-is@npm:^1.6.16": - version: 1.6.18 - resolution: "type-is@npm:1.6.18" - dependencies: - media-typer: "npm:0.3.0" - mime-types: "npm:~2.1.24" - checksum: 10/0bd9eeae5efd27d98fd63519f999908c009e148039d8e7179a074f105362d4fcc214c38b24f6cda79c87e563cbd12083a4691381ed28559220d4a10c2047bed4 - languageName: node - linkType: hard - -"type-is@npm:^2.0.1": - version: 2.0.1 - resolution: "type-is@npm:2.0.1" - dependencies: - content-type: "npm:^1.0.5" - media-typer: "npm:^1.1.0" - mime-types: "npm:^3.0.0" - checksum: 10/bacdb23c872dacb7bd40fbd9095e6b2fca2895eedbb689160c05534d7d4810a7f4b3fd1ae87e96133c505958f6d602967a68db5ff577b85dd6be76eaa75d58af - languageName: node - linkType: hard - -"typedoc-plugin-external-module-map@npm:^2.2.0": - version: 2.2.0 - resolution: "typedoc-plugin-external-module-map@npm:2.2.0" - dependencies: - "@types/node": "npm:^20.14.14" - peerDependencies: - typedoc: ">=0.27 <2.0" - checksum: 10/bf669f336d5680f2dc41c1dd0d8f7cff26623499784d85935100a104282068f93eff94dfb9811fccf99c693f18494509fa7c95f209f9f7f03991bafb58306b2c - languageName: node - linkType: hard - -"typedoc@npm:^0.28.13": - version: 0.28.14 - resolution: "typedoc@npm:0.28.14" - dependencies: - "@gerrit0/mini-shiki": "npm:^3.12.0" - lunr: "npm:^2.3.9" - markdown-it: "npm:^14.1.0" - minimatch: "npm:^9.0.5" - yaml: "npm:^2.8.1" - peerDependencies: - typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x - bin: - typedoc: bin/typedoc - checksum: 10/6b2bb87c85de96475f60ba2a92e0cb7169fef138cce4d91c949267ca9268c4fc97c2db9702f306ad5e594aa5244cc9aa563791ec31dd1b1d5288cf1ab08aa7a0 - languageName: node - linkType: hard - -"typescript@npm:^5.9.3": - version: 5.9.3 - resolution: "typescript@npm:5.9.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/c089d9d3da2729fd4ac517f9b0e0485914c4b3c26f80dc0cffcb5de1719a17951e92425d55db59515c1a7ddab65808466debb864d0d56dcf43f27007d0709594 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": - version: 5.9.3 - resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/696e1b017bc2635f4e0c94eb4435357701008e2f272f553d06e35b494b8ddc60aa221145e286c28ace0c89ee32827a28c2040e3a69bdc108b1a5dc8fb40b72e3 - languageName: node - linkType: hard - -"uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": - version: 2.1.0 - resolution: "uc.micro@npm:2.1.0" - checksum: 10/37197358242eb9afe367502d4638ac8c5838b78792ab218eafe48287b0ed28aaca268ec0392cc5729f6c90266744de32c06ae938549aee041fc93b0f9672d6b2 - languageName: node - linkType: hard - -"undici-types@npm:~6.21.0": - version: 6.21.0 - resolution: "undici-types@npm:6.21.0" - checksum: 10/ec8f41aa4359d50f9b59fa61fe3efce3477cc681908c8f84354d8567bb3701fafdddf36ef6bff307024d3feb42c837cf6f670314ba37fc8145e219560e473d14 - languageName: node - linkType: hard - -"undici-types@npm:~7.14.0": - version: 7.14.0 - resolution: "undici-types@npm:7.14.0" - checksum: 10/0f8709b21437697af35801e33bddbe9992e0cf1771959c41850b1946f63822b825e03ce99f44bf19e4f5c3ccc5166e0be59f541565c36ce86163dc2c5870bc62 - languageName: node - linkType: hard - -"unfetch@npm:^4.2.0": - version: 4.2.0 - resolution: "unfetch@npm:4.2.0" - checksum: 10/d4924178060b6828d858acef3ce2baea69acd3f3f9e2429fd503a0ed0d2b1ed0ee107786aceadfd167ce884fad12d22b5288eb865a3ea036979b8358b8555c9a - languageName: node - linkType: hard - -"unique-filename@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-filename@npm:4.0.0" - dependencies: - unique-slug: "npm:^5.0.0" - checksum: 10/6a62094fcac286b9ec39edbd1f8f64ff92383baa430af303dfed1ffda5e47a08a6b316408554abfddd9730c78b6106bef4ca4d02c1231a735ddd56ced77573df - languageName: node - linkType: hard - -"unique-slug@npm:^5.0.0": - version: 5.0.0 - resolution: "unique-slug@npm:5.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10/beafdf3d6f44990e0a5ce560f8f881b4ee811be70b6ba0db25298c31c8cf525ed963572b48cd03be1c1349084f9e339be4241666d7cf1ebdad20598d3c652b27 - languageName: node - linkType: hard - -"unpipe@npm:1.0.0": - version: 1.0.0 - resolution: "unpipe@npm:1.0.0" - checksum: 10/4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 - languageName: node - linkType: hard - -"url-join@npm:4.0.1": - version: 4.0.1 - resolution: "url-join@npm:4.0.1" - checksum: 10/b53b256a9a36ed6b0f6768101e78ca97f32d7b935283fd29ce19d0bbfb6f88aa80aa6c03fd87f2f8978ab463a6539f597a63051e7086f3379685319a7495f709 - languageName: node - linkType: hard - -"v8-compile-cache-lib@npm:^3.0.1": - version: 3.0.1 - resolution: "v8-compile-cache-lib@npm:3.0.1" - checksum: 10/88d3423a52b6aaf1836be779cab12f7016d47ad8430dffba6edf766695e6d90ad4adaa3d8eeb512cc05924f3e246c4a4ca51e089dccf4402caa536b5e5be8961 - languageName: node - linkType: hard - -"validate-npm-package-name@npm:^4.0.0": - version: 4.0.0 - resolution: "validate-npm-package-name@npm:4.0.0" - dependencies: - builtins: "npm:^5.0.0" - checksum: 10/a32fd537bad17fcb59cfd58ae95a414d443866020d448ec3b22e8d40550cb585026582a57efbe1f132b882eea4da8ac38ee35f7be0dd72988a3cb55d305a20c1 - languageName: node - linkType: hard - -"vary@npm:^1.1.2": - version: 1.1.2 - resolution: "vary@npm:1.1.2" - checksum: 10/31389debef15a480849b8331b220782230b9815a8e0dbb7b9a8369559aed2e9a7800cd904d4371ea74f4c3527db456dc8e7ac5befce5f0d289014dbdf47b2242 - languageName: node - linkType: hard - -"vorpal@npm:^1.12.0": - version: 1.12.0 - resolution: "vorpal@npm:1.12.0" - dependencies: - babel-polyfill: "npm:^6.3.14" - chalk: "npm:^1.1.0" - in-publish: "npm:^2.0.0" - inquirer: "npm:0.11.0" - lodash: "npm:^4.5.1" - log-update: "npm:^1.0.2" - minimist: "npm:^1.2.0" - node-localstorage: "npm:^0.6.0" - strip-ansi: "npm:^3.0.0" - wrap-ansi: "npm:^2.0.0" - checksum: 10/7bc1f40863a7fdcbd6c8a8cf463a59bac5c433c2a836ff86e8574676fa8a2ca17d19806d4aa24cf5a23a3350b59fe9c9a4a430d806ccb245dbb47ebad834d181 - languageName: node - linkType: hard - -"webidl-conversions@npm:^3.0.0": - version: 3.0.1 - resolution: "webidl-conversions@npm:3.0.1" - checksum: 10/b65b9f8d6854572a84a5c69615152b63371395f0c5dcd6729c45789052296df54314db2bc3e977df41705eacb8bc79c247cee139a63fa695192f95816ed528ad - languageName: node - linkType: hard - -"whatwg-url@npm:^5.0.0": - version: 5.0.0 - resolution: "whatwg-url@npm:5.0.0" - dependencies: - tr46: "npm:~0.0.3" - webidl-conversions: "npm:^3.0.0" - checksum: 10/f95adbc1e80820828b45cc671d97da7cd5e4ef9deb426c31bcd5ab00dc7103042291613b3ef3caec0a2335ed09e0d5ed026c940755dbb6d404e2b27f940fdf07 - languageName: node - linkType: hard - -"which@npm:^2.0.1": - version: 2.0.2 - resolution: "which@npm:2.0.2" - dependencies: - isexe: "npm:^2.0.0" - bin: - node-which: ./bin/node-which - checksum: 10/4782f8a1d6b8fc12c65e968fea49f59752bf6302dc43036c3bf87da718a80710f61a062516e9764c70008b487929a73546125570acea95c5b5dcc8ac3052c70f - languageName: node - linkType: hard - -"which@npm:^5.0.0": - version: 5.0.0 - resolution: "which@npm:5.0.0" - dependencies: - isexe: "npm:^3.1.1" - bin: - node-which: bin/which.js - checksum: 10/6ec99e89ba32c7e748b8a3144e64bfc74aa63e2b2eacbb61a0060ad0b961eb1a632b08fb1de067ed59b002cec3e21de18299216ebf2325ef0f78e0f121e14e90 - languageName: node - linkType: hard - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10/cebdaeca3a6880da410f75209e68cd05428580de5ad24535f22696d7d9cab134d1f8498599f344c3cf0fb37c1715807a183778d8c648d6cc0cb5ff2bb4236540 - languageName: node - linkType: hard - -"wrap-ansi@npm:^2.0.0": - version: 2.1.0 - resolution: "wrap-ansi@npm:2.1.0" - dependencies: - string-width: "npm:^1.0.1" - strip-ansi: "npm:^3.0.1" - checksum: 10/cf66d33f62f2edf0aac52685da98194e47ddf4ceb81d9f98f294b46ffbbf8662caa72a905b343aeab8d6a16cade982be5fc45df99235b07f781ebf68f051ca98 - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10/7b1e4b35e9bb2312d2ee9ee7dc95b8cb5f8b4b5a89f7dde5543fe66c1e3715663094defa50d75454ac900bd210f702d575f15f3f17fa9ec0291806d2578d1ddf - languageName: node - linkType: hard - -"wrappy@npm:1": - version: 1.0.2 - resolution: "wrappy@npm:1.0.2" - checksum: 10/159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 - languageName: node - linkType: hard - -"yallist@npm:^4.0.0": - version: 4.0.0 - resolution: "yallist@npm:4.0.0" - checksum: 10/4cb02b42b8a93b5cf50caf5d8e9beb409400a8a4d85e83bb0685c1457e9ac0b7a00819e9f5991ac25ffabb56a78e2f017c1acc010b3a1babfe6de690ba531abd - languageName: node - linkType: hard - -"yallist@npm:^5.0.0": - version: 5.0.0 - resolution: "yallist@npm:5.0.0" - checksum: 10/1884d272d485845ad04759a255c71775db0fac56308764b4c77ea56a20d56679fad340213054c8c9c9c26fcfd4c4b2a90df993b7e0aaf3cdb73c618d1d1a802a - languageName: node - linkType: hard - -"yaml@npm:^2.8.1": - version: 2.8.1 - resolution: "yaml@npm:2.8.1" - bin: - yaml: bin.mjs - checksum: 10/eae07b3947d405012672ec17ce27348aea7d1fa0534143355d24a43a58f5e05652157ea2182c4fe0604f0540be71f99f1173f9d61018379404507790dff17665 - languageName: node - linkType: hard - -"yn@npm:3.1.1": - version: 3.1.1 - resolution: "yn@npm:3.1.1" - checksum: 10/2c487b0e149e746ef48cda9f8bad10fc83693cd69d7f9dcd8be4214e985de33a29c9e24f3c0d6bcf2288427040a8947406ab27f7af67ee9456e6b84854f02dd6 - languageName: node - linkType: hard - -"zod@npm:^3.19.1": - version: 3.25.76 - resolution: "zod@npm:3.25.76" - checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995 - languageName: node - linkType: hard +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cspotcode/source-map-support@^0.8.0": + 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== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@gerrit0/mini-shiki@^3.23.0": + version "3.23.0" + resolved "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz" + integrity sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg== + dependencies: + "@shikijs/engine-oniguruma" "^3.23.0" + "@shikijs/langs" "^3.23.0" + "@shikijs/themes" "^3.23.0" + "@shikijs/types" "^3.23.0" + "@shikijs/vscode-textmate" "^10.0.2" + +"@hapi/bourne@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz" + integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w== + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.5.5" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@koa/router@^14.0.0": + version "14.0.0" + resolved "https://registry.npmjs.org/@koa/router/-/router-14.0.0.tgz" + integrity sha512-LBSu5K0qAaaQcXX/0WIB9PGDevyCxxpnc1uq13vV/CgObaVxuis5hKl3Eboq/8gcb6ebnkAStW9NB/Em2eYyFA== + dependencies: + debug "^4.4.1" + http-errors "^2.0.0" + koa-compose "^4.1.0" + path-to-regexp "^8.2.0" + +"@noble/hashes@^1.1.5": + version "1.8.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + +"@paralleldrive/cuid2@^2.2.2": + version "2.3.1" + resolved "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz" + integrity sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw== + dependencies: + "@noble/hashes" "^1.1.5" + +"@schemastore/package@^0.0.6": + version "0.0.6" + resolved "https://registry.npmjs.org/@schemastore/package/-/package-0.0.6.tgz" + integrity sha512-uNloNHoyHttSSdeuEkkSC+mdxJXMKlcUPOMb//qhQbIQijXg8x54VmAw3jm6GJZQ5DBtIqGBd66zEQCDCChQVA== + +"@shikijs/engine-oniguruma@^3.23.0": + version "3.23.0" + resolved "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz" + integrity sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g== + dependencies: + "@shikijs/types" "3.23.0" + "@shikijs/vscode-textmate" "^10.0.2" + +"@shikijs/langs@^3.23.0": + version "3.23.0" + resolved "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz" + integrity sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg== + dependencies: + "@shikijs/types" "3.23.0" + +"@shikijs/themes@^3.23.0": + version "3.23.0" + resolved "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz" + integrity sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA== + dependencies: + "@shikijs/types" "3.23.0" + +"@shikijs/types@^3.23.0", "@shikijs/types@3.23.0": + version "3.23.0" + resolved "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz" + integrity sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ== + dependencies: + "@shikijs/vscode-textmate" "^10.0.2" + "@types/hast" "^3.0.4" + +"@shikijs/vscode-textmate@^10.0.2": + version "10.0.2" + resolved "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz" + integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg== + +"@skeldjs/au-client@^3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@skeldjs/au-client/-/au-client-3.0.3.tgz" + integrity sha512-ZvjcLCjQcljl2EUtPIUL5uqG1pyjvU1rgL02CxkGc6IFNW3VF+rDHJZHjjsun7gMzyoTOTP+zqTUf/7/5KQQKw== + +"@skeldjs/au-constants@^3.0.3", "@skeldjs/au-constants@3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@skeldjs/au-constants/-/au-constants-3.0.3.tgz" + integrity sha512-A8xD3XuX5eyrw+HWuqNNrMhw1TQ78Vdq8Zypg5cgYxWe12hgpqf22X9XgOkru1M/943u1xmkQoNvASDD0/j38g== + +"@skeldjs/au-core@^3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@skeldjs/au-core/-/au-core-3.0.3.tgz" + integrity sha512-Mobo6sn8CjLDmo9DT1YTQlQMtVzuTQy9+obK5e4vVhhXSDfebl4avk6A+6l9ZW/FqgQfZOARmFpV30vZGdJ7Cg== + dependencies: + "@skeldjs/au-constants" "3.0.3" + "@skeldjs/au-protocol" "3.0.3" + "@skeldjs/events" "3.0.3" + "@skeldjs/hazel" "3.0.3" + +"@skeldjs/au-protocol@^3.0.3", "@skeldjs/au-protocol@3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@skeldjs/au-protocol/-/au-protocol-3.0.3.tgz" + integrity sha512-35KuC7ZOgfMlwn2NSfpabC0di76NAYsqjV2oWhdrmxvXcXPsOgoXx1R4K8V8dsh5Chj9odCysyjUOMbsLkI5mQ== + dependencies: + "@skeldjs/au-constants" "3.0.3" + "@skeldjs/hazel" "3.0.3" + +"@skeldjs/au-text@^3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@skeldjs/au-text/-/au-text-3.0.3.tgz" + integrity sha512-00fKMnEViEPFNOF6q+0eM7IXvYgXcqIDhZGNTbKwTLqWhhwWp0QJgXBBRws4otoAd0/jiyS/zFYq+WzA3Vh57Q== + +"@skeldjs/events@^3.0.3", "@skeldjs/events@3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@skeldjs/events/-/events-3.0.3.tgz" + integrity sha512-ZONbKbfq1aIY8JxSFWBmhxuWcn7mvvEvxM1OUO/e/+0zwQYqWCN5Jwv2u+rumXqPoerVBT1kd/I6JZ2kXaM5/A== + +"@skeldjs/hazel@^3.0.3", "@skeldjs/hazel@3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@skeldjs/hazel/-/hazel-3.0.3.tgz" + integrity sha512-AQCsgcJVBGXSLd1mkgyvpttL3n5QUg7919pc18gmdIWGh8XJ9yl3XoGmg5uUCZXVcpQ21RfrrdL98zEfQQyumQ== + +"@tsconfig/node10@^1.0.7": + version "1.0.12" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz" + integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/accepts@*": + version "1.3.7" + resolved "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz" + integrity sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ== + dependencies: + "@types/node" "*" + +"@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== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/co-body@^6.1.0": + version "6.1.3" + resolved "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz" + integrity sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@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== + +"@types/cookies@*": + version "0.9.2" + resolved "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.2.tgz" + integrity sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A== + dependencies: + "@types/connect" "*" + "@types/express" "*" + "@types/keygrip" "*" + "@types/node" "*" + +"@types/express-serve-static-core@^5.0.0": + version "5.1.1" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz" + integrity sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*": + version "5.0.6" + resolved "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "^2" + +"@types/formidable@^2.0.5": + version "2.0.6" + resolved "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz" + integrity sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w== + dependencies: + "@types/node" "*" + +"@types/hast@^3.0.4": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + +"@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== + +"@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== + +"@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== + +"@types/koa__router@^12": + version "12.0.5" + resolved "https://registry.npmjs.org/@types/koa__router/-/koa__router-12.0.5.tgz" + integrity sha512-1HeLxuDn4n5it1yZYCSyOYXo++73zT0ffoviXnPxbwbxLbvDFEvWD9ZzpRiIpK4oKR0pi+K+Mk/ZjyROjW3HSw== + dependencies: + "@types/koa" "*" + +"@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== + dependencies: + "@types/koa" "*" + +"@types/koa@*", "@types/koa@^2", "@types/koa@^2.13.5": + version "2.15.2" + resolved "https://registry.npmjs.org/@types/koa/-/koa-2.15.2.tgz" + integrity sha512-CB+iyjjh1uS5N6/CKwXvw0qA7USMS2WVc4Tjf660yCjhdvqzNr8gdFcIawB41zGGptOQ+d1fnpaQWIIUXYxR3w== + dependencies: + "@types/accepts" "*" + "@types/content-disposition" "*" + "@types/cookies" "*" + "@types/http-assert" "*" + "@types/http-errors" "*" + "@types/keygrip" "*" + "@types/koa-compose" "*" + "@types/node" "*" + +"@types/minimatch@^3.0.5": + version "3.0.5" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + +"@types/node@*", "@types/node@^24.7.0": + version "24.13.2" + resolved "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz" + integrity sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA== + dependencies: + undici-types "~7.18.0" + +"@types/node@^20.14.14": + version "20.19.43" + resolved "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz" + integrity sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA== + dependencies: + undici-types "~6.21.0" + +"@types/prompts@^2.0.14": + version "2.4.9" + resolved "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz" + integrity sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA== + dependencies: + "@types/node" "*" + kleur "^3.0.3" + +"@types/qs@*": + version "6.15.1" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz" + integrity sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw== + +"@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== + +"@types/send@*": + version "1.2.1" + resolved "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + +"@types/triple-beam@^1.3.2": + version "1.3.5" + resolved "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== + +"@types/unist@*": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/vorpal@^1.12.0": + version "1.12.8" + resolved "https://registry.npmjs.org/@types/vorpal/-/vorpal-1.12.8.tgz" + integrity sha512-Qt+Yxa1q6QCaYMxZFXlyPOF3ktIscTelNr1AFYuKM7/Dhlki4gvc476uFyA/hYvskSA6V8W+55x9FjlbAPcYdQ== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.0": + version "17.0.35" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== + dependencies: + "@types/yargs-parser" "*" + +accepts@^1.3.8: + 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== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-walk@^8.1.1: + version "8.3.5" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz" + integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1: + version "8.17.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz" + integrity sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg== + +ansi-escapes@^1.0.0, ansi-escapes@^1.1.0: + version "1.4.0" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz" + integrity sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +babel-polyfill@^6.3.14: + version "6.26.0" + resolved "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz" + integrity sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ== + dependencies: + babel-runtime "^6.26.0" + core-js "^2.5.0" + regenerator-runtime "^0.10.5" + +babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz" + integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^1.1.7: + version "1.1.15" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz" + integrity sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^5.0.5: + version "5.0.6" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz" + integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== + dependencies: + balanced-match "^4.0.2" + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +builtins@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz" + integrity sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg== + dependencies: + semver "^7.0.0" + +bytes@~3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + 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== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +chalk@^1.0.0: + version "1.1.3" + resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^1.1.0: + version "1.1.3" + resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^4.1.1: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cli-cursor@^1.0.1, cli-cursor@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz" + integrity sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A== + dependencies: + restore-cursor "^1.0.1" + +cli-width@^1.0.1: + version "1.1.1" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz" + integrity sha512-eMU2akIeEIkCxGXUNmDnJq1KzOIiPnJ+rKqRe6hcxE3vIOPvpMrBYOn/Bl7zNlYJj/zQxXquAnozHUCf9Whnsg== + +co-body@^6.1.0: + version "6.2.0" + resolved "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz" + integrity sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA== + dependencies: + "@hapi/bourne" "^3.0.0" + inflation "^2.0.0" + qs "^6.5.2" + raw-body "^2.3.3" + type-is "^1.6.16" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz" + integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +compare-versions@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz" + integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz" + integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== + +content-type@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +content-type@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz" + integrity sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ== + +cookies@~0.9.1: + version "0.9.1" + resolved "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz" + integrity sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw== + dependencies: + depd "~2.0.0" + keygrip "~1.1.0" + +core-js@^2.4.0, core-js@^2.5.0: + version "2.6.12" + resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +debug@^4.4.1: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +deep-equal@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz" + integrity sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +depd@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +dezalgo@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + +diff@^4.0.1: + version "4.0.4" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz" + integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.2" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz" + integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw== + dependencies: + es-errors "^1.3.0" + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz" + integrity sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg== + +figures@^1.3.5: + version "1.7.0" + resolved "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz" + integrity sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ== + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +formidable@^2.0.1: + version "2.1.5" + resolved "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz" + integrity sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q== + dependencies: + "@paralleldrive/cuid2" "^2.2.2" + dezalgo "^1.0.4" + once "^1.4.0" + qs "^6.11.0" + +fresh@~0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fromentries@^1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" + integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== + dependencies: + ansi-regex "^2.0.0" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.2: + version "2.0.4" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz" + integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== + dependencies: + function-bind "^1.1.2" + +http-assert@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz" + integrity sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w== + dependencies: + deep-equal "~1.0.1" + http-errors "~1.8.0" + +http-errors@^2.0.0, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + +http-errors@~1.8.0: + version "1.8.1" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.1" + +iconv-lite@~0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +in-publish@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz" + integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ== + +inflation@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz" + integrity sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ== + +inherits@~2.0.4, inherits@2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@0.11.0: + version "0.11.0" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-0.11.0.tgz" + integrity sha512-LIwC+g/fJbmKhDm341+RqDIV4jPf/n3pMway9xg8Ovt6CCQo1ozXhmuKTcoNIWhWJJKsSGZP+Rnuq7JgM7mE2A== + dependencies: + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^1.0.1" + figures "^1.3.5" + lodash "^3.3.1" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + strip-ansi "^3.0.0" + through "^2.3.6" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" + integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== + dependencies: + number-is-nan "^1.0.0" + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +isomorphic-unfetch@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" + +keygrip@~1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz" + integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ== + dependencies: + tsscmp "1.0.6" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +koa-body@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/koa-body/-/koa-body-6.0.1.tgz" + integrity sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg== + dependencies: + "@types/co-body" "^6.1.0" + "@types/formidable" "^2.0.5" + "@types/koa" "^2.13.5" + co-body "^6.1.0" + formidable "^2.0.1" + zod "^3.19.1" + +koa-compose@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz" + integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw== + +koa@^3.0.3: + version "3.2.1" + resolved "https://registry.npmjs.org/koa/-/koa-3.2.1.tgz" + integrity sha512-e7IpWJrnanNUroVK2taAgMxoEZvHLXdQiNjeExSu/DEIWm83jaKGBgb7tLmu2rMYpA027qFB3iLR/k3AVpFRnA== + dependencies: + accepts "^1.3.8" + content-disposition "~1.0.1" + content-type "^1.0.5" + cookies "~0.9.1" + delegates "^1.0.0" + destroy "^1.2.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + fresh "~0.5.2" + http-assert "^1.5.0" + http-errors "^2.0.0" + koa-compose "^4.1.0" + mime-types "^3.0.1" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +linkify-it@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz" + integrity sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg== + dependencies: + uc.micro "^2.0.0" + +lodash@^3.3.1: + version "3.10.1" + resolved "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + integrity sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ== + +lodash@^4.5.1: + version "4.18.1" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== + +log-update@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz" + integrity sha512-4vSow8gbiGnwdDNrpy1dyNaXWKSCIPop0EHdE8GrnngHoJujM3QhvHUN/igsYCgPoHo7pFOezlJ61Hlln0KHyA== + dependencies: + ansi-escapes "^1.0.0" + cli-cursor "^1.0.2" + +lunr@^2.3.9: + version "2.3.9" + resolved "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz" + integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +markdown-it@^14.1.1: + version "14.2.0" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz" + integrity sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ== + dependencies: + argparse "^2.0.1" + entities "^4.4.0" + linkify-it "^5.0.1" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mdurl@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz" + integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^3.0.0, mime-types@^3.0.1: + version "3.0.2" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + +mime-types@~2.1.24: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@^10.2.5: + version "10.2.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== + dependencies: + brace-expansion "^5.0.5" + +minimatch@^3.0.4: + version "3.1.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mute-stream@0.0.5: + version "0.0.5" + resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz" + integrity sha512-EbrziT4s8cWPmzr47eYVW3wimS4HsvlnV5ri1xw1aR6JQo/OrJX5rkl32K/QQHdxeabJETtfeaROGhd8W7uBgg== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +node-fetch@^2.6.1: + version "2.7.0" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-localstorage@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/node-localstorage/-/node-localstorage-0.6.0.tgz" + integrity sha512-t9dKMce8qUs2KK02ZiBgzZSykUxc+5UcML7/20a62ruHwfh7+bNQvrH/auxY5gFNexTwAFdr+DbptxlLq4+7qQ== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" + integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.3, object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz" + integrity sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A== + +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@^8.2.0: + version "8.4.2" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz" + integrity sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.2" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz" + integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== + +pinst@^2.1.6: + version "2.1.6" + resolved "https://registry.npmjs.org/pinst/-/pinst-2.1.6.tgz" + integrity sha512-B4dYmf6nEXg1NpDSB+orYWvKa5Kfmz5KzWC29U59dpVM4S/+xp0ak/JMEsw04UQTNNKps7klu0BUalr343Gt9g== + dependencies: + fromentries "^1.3.2" + +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + +qs@^6.11.0, qs@^6.5.2: + version "6.15.2" + resolved "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz" + integrity sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw== + dependencies: + side-channel "^1.1.0" + +query-registry@^2.5.0: + version "2.6.0" + resolved "https://registry.npmjs.org/query-registry/-/query-registry-2.6.0.tgz" + integrity sha512-Z5oNq7qH0g96qBTx2jAvS0X71hKP4tETtSJKEl6BdihzYqh9QKiJQBMT7qIQuzxR9lxfiso+aXCFhZ+EcAoppQ== + dependencies: + isomorphic-unfetch "^3.1.0" + make-error "^1.3.6" + tiny-lru "^8.0.2" + url-join "4.0.1" + validate-npm-package-name "^4.0.0" + +raw-body@^2.3.3: + version "2.5.3" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz" + integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== + dependencies: + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + unpipe "~1.0.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +readline2@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz" + integrity sha512-8/td4MmwUB6PkZUbV25uKz7dfrmjYWxsW8DVfibWdlHRk/l/DfHKn4pU+dfcoGLFgWOdyGCzINRQD7jn+Bv+/g== + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + mute-stream "0.0.5" + +reflect-metadata@^0.1.13: + version "0.1.14" + resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz" + integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A== + +regenerator-runtime@^0.10.5: + version "0.10.5" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz" + integrity sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-2.0.0.tgz" + integrity sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ== + dependencies: + resolve-from "^5.0.0" + +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz" + integrity sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw== + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + +run-async@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz" + integrity sha512-qOX+w+IxFgpUpJfkv2oGN0+ExPs68F4sZHfaRRx4dDexAQkG83atugKVEylyT5ARees3HBbfmuvnjbrd8j9Wjw== + dependencies: + once "^1.3.0" + +rx-lite@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz" + integrity sha512-1I1+G2gteLB8Tkt8YI1sJvSIfa0lWuRtC8GjvtyPBcLSF5jBCCJJqKrpER5JU5r6Bhe+i9/pK3VMuUcXu0kdwQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^7.0.0: + version "7.8.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz" + integrity sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA== + +setprototypeof@~1.2.0, setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel-list@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + +side-channel-map@^1.0.1: + 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== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz" + integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + side-channel-list "^1.0.1" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +statuses@^2.0.1, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +"statuses@>= 1.5.0 < 2": + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" + integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tiny-lru@^8.0.2: + version "8.0.2" + resolved "https://registry.npmjs.org/tiny-lru/-/tiny-lru-8.0.2.tgz" + integrity sha512-ApGvZ6vVvTNdsmt676grvCkUCGwzG9IqXma5Z07xJgiC5L7akUMof5U8G2JTI9Rz/ovtVhJBlY6mNhEvtjzOIg== + +to-regex-range@^5.0.1: + 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== + dependencies: + is-number "^7.0.0" + +toidentifier@~1.0.1, toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + +ts-node@^10.0.0: + version "10.9.2" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsscmp@1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz" + integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== + +type-is@^1.6.16: + version "1.6.18" + resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +type-is@^2.0.1: + version "2.1.0" + resolved "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz" + integrity sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA== + dependencies: + content-type "^2.0.0" + media-typer "^1.1.0" + mime-types "^3.0.0" + +typedoc-plugin-external-module-map@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/typedoc-plugin-external-module-map/-/typedoc-plugin-external-module-map-2.2.0.tgz" + integrity sha512-wWou92pJOMJk6cpUpbIEsBjtZKGLnu9UsVamXhndhkyBBvOBxaS+dx8USPDYByuy1Xx05V36IF+GOLqjPTpfyw== + dependencies: + "@types/node" "^20.14.14" + +typedoc@^0.28.13, "typedoc@>=0.27 <2.0": + version "0.28.19" + resolved "https://registry.npmjs.org/typedoc/-/typedoc-0.28.19.tgz" + integrity sha512-wKh+lhdmMFivMlc6vRRcMGXeGEHGU2g8a2CkPTJjJlwRf1iXbimWIPcFolCqe4E0d/FRtGszpIrsp3WLpDB8Pw== + dependencies: + "@gerrit0/mini-shiki" "^3.23.0" + lunr "^2.3.9" + markdown-it "^14.1.1" + minimatch "^10.2.5" + yaml "^2.8.3" + +typescript@^5.9.3, typescript@>=2.7, "typescript@5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x": + version "5.9.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.18.0: + version "7.18.2" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz" + integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== + +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +url-join@4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + +v8-compile-cache-lib@^3.0.1: + 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== + +validate-npm-package-name@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz" + integrity sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q== + dependencies: + builtins "^5.0.0" + +vary@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vorpal@^1.12.0: + version "1.12.0" + resolved "https://registry.npmjs.org/vorpal/-/vorpal-1.12.0.tgz" + integrity sha512-lYEhd75l75P3D1LKpm4KqdOSpNyNdDJ9ixEZmC5ZAZUKGy6JNexfMdQ9SNaT5pCHuzuXXRJQedJ+CdqNg/D4Kw== + dependencies: + babel-polyfill "^6.3.14" + chalk "^1.1.0" + in-publish "^2.0.0" + inquirer "0.11.0" + lodash "^4.5.1" + log-update "^1.0.2" + minimist "^1.2.0" + node-localstorage "^0.6.0" + strip-ansi "^3.0.0" + wrap-ansi "^2.0.0" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz" + integrity sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw== + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yaml@^2.8.3: + version "2.9.0" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz" + integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +zod@^3.19.1: + version "3.25.76" + resolved "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== From 5ba8e6e9a5e768490e729216f5aa14f20c37d963 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Fri, 19 Jun 2026 05:09:39 +0800 Subject: [PATCH 02/17] New Version Add and New GitHub URL --- Dockerfile | 62 ++++++++++++++++++++++++++++++++++++++ bin/createDefaultConfig.ts | 15 ++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..f70792495 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,62 @@ +# ── Stage 1: Build SkeldJS from source ── +FROM node:22-alpine AS skeldjs-builder + +WORKDIR /skeldjs +RUN apk add --no-cache git + +# Clone the SkeldJS fork +ARG SKELDJS_REPO=https://github.com/Empostor/SkeldJS +ARG SKELDJS_BRANCH=main +RUN git clone --depth 1 --branch ${SKELDJS_BRANCH} ${SKELDJS_REPO} . + +# Install dependencies & build all packages +RUN corepack enable && yarn install +RUN yarn build + +# ── Stage 2: Build Waterway with local SkeldJS ── +FROM node:22-alpine AS builder + +WORKDIR /app + +# Copy SkeldJS built packages from previous stage +COPY --from=skeldjs-builder /skeldjs/packages /skeldjs/packages +COPY --from=skeldjs-builder /skeldjs/package.json /skeldjs/ +COPY --from=skeldjs-builder /skeldjs/tsconfig.json /skeldjs/ + +# Copy Waterway source +COPY package.json tsconfig.json .yarnrc.yml ./ +COPY .yarn/ .yarn/ +COPY bin/ bin/ +COPY src/ src/ + +# Enable yarn +RUN corepack enable + +# Rewrite @skeldjs dependencies to use local linked packages +# This replaces npm registry deps with file: references to the locally built SkeldJS +RUN sed -i \ + -e 's|"@skeldjs/au-client": "[^"]*"|"@skeldjs/au-client": "link:/skeldjs/packages/client"|' \ + -e 's|"@skeldjs/au-constants": "[^"]*"|"@skeldjs/au-constants": "link:/skeldjs/packages/constant"|' \ + -e 's|"@skeldjs/au-core": "[^"]*"|"@skeldjs/au-core": "link:/skeldjs/packages/core"|' \ + -e 's|"@skeldjs/au-protocol": "[^"]*"|"@skeldjs/au-protocol": "link:/skeldjs/packages/protocol"|' \ + -e 's|"@skeldjs/au-text": "[^"]*"|"@skeldjs/au-text": "link:/skeldjs/packages/text"|' \ + -e 's|"@skeldjs/events": "[^"]*"|"@skeldjs/events": "link:/skeldjs/packages/events"|' \ + -e 's|"@skeldjs/hazel": "[^"]*"|"@skeldjs/hazel": "link:/skeldjs/packages/hazel"|' \ + package.json + +# Install and build Waterway +RUN yarn install +RUN yarn build + +# ── Stage 3: Production image ── +FROM node:22-alpine + +WORKDIR /app + +COPY --from=builder /app/dist /app/dist +COPY --from=builder /app/node_modules /app/node_modules +COPY --from=builder /app/package.json /app/ + +EXPOSE 22023 22123 + +CMD ["node", "dist/bin/bootstrap"] diff --git a/bin/createDefaultConfig.ts b/bin/createDefaultConfig.ts index 374755883..2a7c4f893 100644 --- a/bin/createDefaultConfig.ts +++ b/bin/createDefaultConfig.ts @@ -9,7 +9,20 @@ export function createDefaultConfig(): WaterwayConfig { autoUpdate: false, exitConfirmation: true, defaultLanguage: "en", - acceptedVersions: ["2025.7.15"], + acceptedVersions: [ + "2025.7.15", // 17.0.0 (2025-09-09) + "2025.9.12", // 17.0.1 (2025-10-14) + "2025.10.9", // 17.1 (2025-11-18) + "2025.11.6", // 17.1.1 (2025-12-03, mobile only) + "2025.12.8", // 17.1.2 (2025-12-11, mobile only) + "2025.11.5", // 17.2 (2026-02-17, build 6630) + "2026.1.22", // 17.2 (2026-02-19, build 6686, hotfix) + "2026.2.2", // 17.2.2 (2026-03-17, build 6768) + "2026.1.12", // 17.3 (2026-03-31, build 6803) + "2026.3.17", // 17.3.1 (2026-04-08, build 6841, mobile only) + "2026.3.18", // 17.4 (2026-06-05, build 7044, pc only) + "2026.4.23", // 17.4 (2026-06-05, build 7045, mobile only) + ], matchmaker: { port: 22023 }, From dbae59c3a8341ad8d8562cfc94b4449755345f09 Mon Sep 17 00:00:00 2001 From: Fang-Kaui <145177672+FangkuaiYa@users.noreply.github.com> Date: Fri, 19 Jun 2026 05:26:06 +0800 Subject: [PATCH 03/17] Modify Dockerfile for flexible SkeldJS cloning Updated Dockerfile to allow branch specification for cloning SkeldJS. --- Dockerfile | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index f70792495..7a5436297 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,14 +4,19 @@ FROM node:22-alpine AS skeldjs-builder WORKDIR /skeldjs RUN apk add --no-cache git -# Clone the SkeldJS fork +# Clone the SkeldJS fork — uses default branch (master) +# To specify a branch: docker build --build-arg SKELDJS_REF=my-branch . ARG SKELDJS_REPO=https://github.com/Empostor/SkeldJS -ARG SKELDJS_BRANCH=main -RUN git clone --depth 1 --branch ${SKELDJS_BRANCH} ${SKELDJS_REPO} . +ARG SKELDJS_REF= +RUN if [ -n "${SKELDJS_REF}" ]; then \ + git clone --depth 1 --branch "${SKELDJS_REF}" ${SKELDJS_REPO} . ; \ + else \ + git clone --depth 1 ${SKELDJS_REPO} . ; \ + fi -# Install dependencies & build all packages +# Install dependencies & build all packages (SkeldJS monorepo uses build-all) RUN corepack enable && yarn install -RUN yarn build +RUN yarn build-all # ── Stage 2: Build Waterway with local SkeldJS ── FROM node:22-alpine AS builder From 73da79ba41d03b3f75677faeb1e8015c54036d6e Mon Sep 17 00:00:00 2001 From: Fang-Kaui <145177672+FangkuaiYa@users.noreply.github.com> Date: Fri, 19 Jun 2026 05:37:06 +0800 Subject: [PATCH 04/17] Add files via upload --- src/index.ts | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 85af84794..7053b62f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,43 @@ export * from "./WaterwayServer"; export * from "./i18n"; -export * from "@skeldjs/au-core"; export * from "@skeldjs/au-protocol"; -export * from "@skeldjs/hazel"; \ No newline at end of file +export * from "@skeldjs/hazel"; + +// Re-export from @skeldjs/au-core explicitly to avoid ambiguity +// with Waterway's own Player* events (PlayerLevelChangedEvent, etc.) +export type { Player, PlayerControl, NetworkedPlayerInfo, MeetingHud, NetworkedObject } from "@skeldjs/au-core"; +export type { PlayerJoinEvent, PlayerResolvable, PlayerSceneChangeEvent } from "@skeldjs/au-core"; +export type { PlayerSetAuthoritativeEvent, RoomEndGameIntentEvent, RoomFixedUpdateEvent } from "@skeldjs/au-core"; +export type { StatefulRoom, StatefulRoomEvents } from "@skeldjs/au-core"; + +export { + AlterGameTag, + Color, + colorData, + CustomNetworkTransform, + DataState, + DisconnectReason, + EndGameIntent, + GameDataMessageTag, + GameMap, + GameMode, + GameOverReason, + GameState, + Hat, + KillDistance, + Language, + Platform, + QuickChatMode, + RoleType, + RoleTeamType, + RpcMessageTag, + SendOption, + Skin, + SpawnFlag, + SpawnType, + SpecialOwnerId, + SystemType, + TaskBarMode, + Visor, +} from "@skeldjs/au-core"; From df4395c342f38ab5b4121032beba8d603a14ce79 Mon Sep 17 00:00:00 2001 From: Fang-Kaui <145177672+FangkuaiYa@users.noreply.github.com> Date: Fri, 19 Jun 2026 05:37:42 +0800 Subject: [PATCH 05/17] Refactor Dockerfile for improved clarity and structure --- Dockerfile | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7a5436297..870a96b71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,53 +4,54 @@ FROM node:22-alpine AS skeldjs-builder WORKDIR /skeldjs RUN apk add --no-cache git -# Clone the SkeldJS fork — uses default branch (master) -# To specify a branch: docker build --build-arg SKELDJS_REF=my-branch . +# Clone the SkeldJS fork — uses default branch +# To force re-clone: docker build --build-arg CACHEBUST=$(date +%s) . +# To specify a branch: docker build --build-arg SKELDJS_REF=my-branch . ARG SKELDJS_REPO=https://github.com/Empostor/SkeldJS ARG SKELDJS_REF= +ARG CACHEBUST=1 RUN if [ -n "${SKELDJS_REF}" ]; then \ git clone --depth 1 --branch "${SKELDJS_REF}" ${SKELDJS_REPO} . ; \ else \ git clone --depth 1 ${SKELDJS_REPO} . ; \ fi -# Install dependencies & build all packages (SkeldJS monorepo uses build-all) +# Install dependencies & build all packages RUN corepack enable && yarn install RUN yarn build-all -# ── Stage 2: Build Waterway with local SkeldJS ── +# ── Stage 2: Build Waterway ── FROM node:22-alpine AS builder WORKDIR /app -# Copy SkeldJS built packages from previous stage -COPY --from=skeldjs-builder /skeldjs/packages /skeldjs/packages -COPY --from=skeldjs-builder /skeldjs/package.json /skeldjs/ -COPY --from=skeldjs-builder /skeldjs/tsconfig.json /skeldjs/ - # Copy Waterway source COPY package.json tsconfig.json .yarnrc.yml ./ COPY .yarn/ .yarn/ COPY bin/ bin/ COPY src/ src/ -# Enable yarn +# Enable yarn, install only third-party deps (SkeldJS from npm as placeholder) RUN corepack enable +RUN yarn install -# Rewrite @skeldjs dependencies to use local linked packages -# This replaces npm registry deps with file: references to the locally built SkeldJS -RUN sed -i \ - -e 's|"@skeldjs/au-client": "[^"]*"|"@skeldjs/au-client": "link:/skeldjs/packages/client"|' \ - -e 's|"@skeldjs/au-constants": "[^"]*"|"@skeldjs/au-constants": "link:/skeldjs/packages/constant"|' \ - -e 's|"@skeldjs/au-core": "[^"]*"|"@skeldjs/au-core": "link:/skeldjs/packages/core"|' \ - -e 's|"@skeldjs/au-protocol": "[^"]*"|"@skeldjs/au-protocol": "link:/skeldjs/packages/protocol"|' \ - -e 's|"@skeldjs/au-text": "[^"]*"|"@skeldjs/au-text": "link:/skeldjs/packages/text"|' \ - -e 's|"@skeldjs/events": "[^"]*"|"@skeldjs/events": "link:/skeldjs/packages/events"|' \ - -e 's|"@skeldjs/hazel": "[^"]*"|"@skeldjs/hazel": "link:/skeldjs/packages/hazel"|' \ - package.json +# Overwrite npm SkeldJS with locally-built packages +COPY --from=skeldjs-builder /skeldjs/packages/client/dist /app/node_modules/@skeldjs/au-client/dist +COPY --from=skeldjs-builder /skeldjs/packages/client/package.json /app/node_modules/@skeldjs/au-client/ +COPY --from=skeldjs-builder /skeldjs/packages/constant/dist /app/node_modules/@skeldjs/au-constants/dist +COPY --from=skeldjs-builder /skeldjs/packages/constant/package.json /app/node_modules/@skeldjs/au-constants/ +COPY --from=skeldjs-builder /skeldjs/packages/core/dist /app/node_modules/@skeldjs/au-core/dist +COPY --from=skeldjs-builder /skeldjs/packages/core/package.json /app/node_modules/@skeldjs/au-core/ +COPY --from=skeldjs-builder /skeldjs/packages/protocol/dist /app/node_modules/@skeldjs/au-protocol/dist +COPY --from=skeldjs-builder /skeldjs/packages/protocol/package.json /app/node_modules/@skeldjs/au-protocol/ +COPY --from=skeldjs-builder /skeldjs/packages/text/dist /app/node_modules/@skeldjs/au-text/dist +COPY --from=skeldjs-builder /skeldjs/packages/text/package.json /app/node_modules/@skeldjs/au-text/ +COPY --from=skeldjs-builder /skeldjs/packages/events/dist /app/node_modules/@skeldjs/events/dist +COPY --from=skeldjs-builder /skeldjs/packages/events/package.json /app/node_modules/@skeldjs/events/ +COPY --from=skeldjs-builder /skeldjs/packages/hazel/dist /app/node_modules/@skeldjs/hazel/dist +COPY --from=skeldjs-builder /skeldjs/packages/hazel/package.json /app/node_modules/@skeldjs/hazel/ -# Install and build Waterway -RUN yarn install +# Build Waterway with local SkeldJS RUN yarn build # ── Stage 3: Production image ── From 62c4fedd3e7a3c4deb4cfe19af78eb0b342729d6 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Fri, 19 Jun 2026 06:19:31 +0800 Subject: [PATCH 06/17] 1 +fix: room privacy inverted on join, missing QuickChat in game listing 2 + 3 +=== [FIX] Room Privacy Inverted (src/Room.ts) === 4 + - finishJoiningGame() sent AlterGame(ChangePrivacy, 1) when room was Private 5 + because the ternary mapped RoomPrivacy.Private(=1) -> value 1, but in the 6 + Among Us protocol, value 1 means Public. 7 + - Result: every newly-joined player received an initial AlterGame telling them 8 + the room was public when it was actually private. Manual toggle by host 9 + fixed it because handleAlterGameMessage() (correct path) was then used. 10 + - Fix: change ternary from "RoomPrivacy.Private ? 1 : 0" to 11 + "RoomPrivacy.Public ? 1 : 0" 12 + 13 +=== [FIX] Missing QuickChat in HTTP Matchmaker (src/Matchmaker.ts) === 14 + - GameListingJson type was missing the QuickChat field that Among Us clients 15 + read from the HTTP /api/games response to determine chat mode. 16 + - getGameListing() now sets QuickChat from the host connection's chatMode. 17 + - Without this, joining clients defaulted to QuickChatOnly display. 18 + 19 +=== [FIX] Missing Language field in UDP GameListing (src/WaterwayServer.ts) === 20 + - UDP game listing now passes room.settings.keywords as Language to the 21 + GameListing constructor (11th arg, cast to any for npm compat until 22 + SkeldJS protocol update is published). --- changelog.txt | 23 +++++++++++++++++++++++ src/Matchmaker.ts | 4 ++++ src/Room.ts | 2 +- src/WaterwayServer.ts | 9 ++++++--- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 8f1433171..f56660ef7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,26 @@ +fix: room privacy inverted on join, missing QuickChat in game listing + +=== [FIX] Room Privacy Inverted (src/Room.ts) === + - finishJoiningGame() sent AlterGame(ChangePrivacy, 1) when room was Private + because the ternary mapped RoomPrivacy.Private(=1) -> value 1, but in the + Among Us protocol, value 1 means Public. + - Result: every newly-joined player received an initial AlterGame telling them + the room was public when it was actually private. Manual toggle by host + fixed it because handleAlterGameMessage() (correct path) was then used. + - Fix: change ternary from "RoomPrivacy.Private ? 1 : 0" to + "RoomPrivacy.Public ? 1 : 0" + +=== [FIX] Missing QuickChat in HTTP Matchmaker (src/Matchmaker.ts) === + - GameListingJson type was missing the QuickChat field that Among Us clients + read from the HTTP /api/games response to determine chat mode. + - getGameListing() now sets QuickChat from the host connection's chatMode. + - Without this, joining clients defaulted to QuickChatOnly display. + +=== [FIX] Missing Language field in UDP GameListing (src/WaterwayServer.ts) === + - UDP game listing now passes room.settings.keywords as Language to the + GameListing constructor (11th arg, cast to any for npm compat until + SkeldJS protocol update is published). + feat: game options validation, Hide & Seek manager, role system, API events, matchmaker improvements, and config updates === Game Options Validation (src/game/GameOptions.ts) [NEW] === diff --git a/src/Matchmaker.ts b/src/Matchmaker.ts index 3f550b039..988d3b269 100644 --- a/src/Matchmaker.ts +++ b/src/Matchmaker.ts @@ -27,6 +27,7 @@ export type GameListingJson = { Platform: number; HostPlatformName: string; Language: number; + QuickChat: number; Options: string; } @@ -375,6 +376,9 @@ export class Matchmaker { Platform: room.playerAuthority?.platform.platformTag || Platform.Unknown, HostPlatformName: room.playerAuthority?.platform.platformName || "UNKNOWN", Language: room.settings.keywords, + QuickChat: room.playerAuthority + ? (room.getConnection(room.playerAuthority)?.chatMode ?? QuickChatMode.FreeChat) + : QuickChatMode.FreeChat, Options: settingsWriter.toString("base64"), }; return gameListing; diff --git a/src/Room.ts b/src/Room.ts index 63de9d080..ed6ba593b 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -1416,7 +1416,7 @@ export class Room extends StatefulRoom { new AlterGameMessage( this.code.id, AlterGameTag.ChangePrivacy, - this.privacy === RoomPrivacy.Private ? 1 : 0 + this.privacy === RoomPrivacy.Public ? 1 : 0 ) ] ) diff --git a/src/WaterwayServer.ts b/src/WaterwayServer.ts index 63ea487c2..0d5e69500 100644 --- a/src/WaterwayServer.ts +++ b/src/WaterwayServer.ts @@ -1704,7 +1704,9 @@ export class WaterwayServer extends EventEmitter { continue; const roomAge = Math.floor((Date.now() - room.createdAt) / 1000); - const gameListing = new GameListing( + // NOTE: The 11-arg GameListing(language) will work once SkeldJS + // with the Language field lands in node_modules (via Docker COPY or npm publish). + const gameListing = new (GameListing as any)( room.code.id, listingIp, this.config.socket.port, @@ -1717,8 +1719,9 @@ export class WaterwayServer extends EventEmitter { room.playerAuthority?.platform || new PlatformSpecificData( Platform.Unknown, "UNKNOWN" - ) - ); + ), + room.settings.keywords + ) as GameListing; if (ignoreSearchTerms === true) { gamesAndRelevance.push([0, gameListing]); From e4c2e179bd4e1cc7f212bafbcc8ca2f6e62320a8 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Fri, 19 Jun 2026 06:54:03 +0800 Subject: [PATCH 07/17] Fix --- changelog.txt | 10 +++++++++- src/Room.ts | 14 +++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index f56660ef7..62bc603ff 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,4 @@ -fix: room privacy inverted on join, missing QuickChat in game listing +fix: game restart state corruption, room privacy inverted, missing QuickChat === [FIX] Room Privacy Inverted (src/Room.ts) === - finishJoiningGame() sent AlterGame(ChangePrivacy, 1) when room was Private @@ -16,6 +16,14 @@ fix: room privacy inverted on join, missing QuickChat in game listing - getGameListing() now sets QuickChat from the host connection's chatMode. - Without this, joining clients defaulted to QuickChatOnly display. +=== [FIX] Game restart state corruption (src/Room.ts) === + - _reset() was not being called between games, causing Duplicate NetID + crash because lastNetId kept incrementing across games. + - handleStartGame() now calls _reset() when gameState === Ended. + - _reset() no longer clears this.players — lobby connections must persist. + - _reset() now resets: lastNetId→100000, networkedObjects, objectList, + roleManager, gameModeManager, ownershipGuards, messageStream, settings. + === [FIX] Missing Language field in UDP GameListing (src/WaterwayServer.ts) === - UDP game listing now passes room.settings.keywords as Language to the GameListing constructor (11th arg, cast to any for npm compat until diff --git a/src/Room.ts b/src/Room.ts index ed6ba593b..988c7f9da 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -550,13 +550,16 @@ export class Room extends StatefulRoom { this.gameModeManager = null; } this.roleManager.handleGameEnd(); - this.players.clear(); + // Don't clear this.players — lobby connections stay + // Clear only game-specific object state (players/connections stay) this.networkedObjects.clear(); + this.objectList.length = 0; this.messageStream = []; - this.authorityId = 0; this.settings = new GameSettings; this.startGameCounter = -1; this.privacy = RoomPrivacy.Private; + this.lastNetId = 100000; + this.ownershipGuards.clear(); } async emit( @@ -1608,8 +1611,13 @@ export class Room extends StatefulRoom { } async handleStartGame(startedBy?: Player) { + // Reset room state if restarting after a previous game ended + if (this.gameState === GameState.Ended) { + this._reset(); + } + this.gameState = GameState.Started; - + if (startedBy) { this.logger.info("Player %s requested game start, to be managed by %s", startedBy, this.isAuthoritative ? "server" : (this.playerAuthority || "unknown player")); From aa59f2d76e0c0a2f51f477e249f15c8e88a887f7 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sat, 20 Jun 2026 21:23:02 +0800 Subject: [PATCH 08/17] Fix alignment with Impostor (C# Among Us Server), implementing account system, role completion, and RPC fixes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - handleRpcMessage(): In SaaH mode, block the original message broadcast after receiving ReportDeadBody - The server has already initiated StartMeeting + MeetingHud spawn via startMeetingWithAuth - Prevent the client from receiving duplicate ReportDeadBody + StartMeeting, eliminating double report UI - Fix the issue where non-host players cannot enter the meeting - handleRpcMessage(): Add import for ReportDeadBodyMessage - processFixedUpdate(): Call roleManager.handleFixedUpdate() to drive role timers - processFixedUpdate(): Call gameModeManager's fixed update (type-safe check) - player.startmeeting event: Add roleManager.handleMeetingStart() call - Add ReportDeadBodyMessage to @skeldjs/au-protocol imports - Three-level cache matching strategy: (IP + username) → username only → IP only - Support expiration cleanup, manual removal, on-demand lookup - Default TTL: 10 minutes - Storage: puid, friendCode, username, ip, clientVersion, expiresAt - New fields: puid: string, friendCode: string, isAuthenticated: boolean - Constructor initializes them as empty - disconnect() cleanup resets all authentication fields - Parse JWT from Authorization: Bearer header - extractPuidFromJwt(): Base64URL-decode JWT payload, extract sub claim - Call Innersloth backend to obtain FriendCode - fetchFriendCodeFromBackend(): HTTPS GET backend.innersloth.com/api/user/username - Support both JSON:API and flat response formats - 15-second timeout - generateFallbackFriendCode(): Generate stable fallback code based on SHA-256 hash of PUID - Store in AuthCache for UDP handshake matching - Support X-Real-IP / X-Forwarded-For reverse proxy headers - Preserve body.Puid fallback path (legacy) - New imports: https, AuthCache - handleHello(): After client authentication, look up AuthCache by (remoteIP, username) - On match: set connection.puid, connection.friendCode, connection.isAuthenticated - Remove from cache after match (single-use) - Three-tier fallback: IP+username → username → IP - handleJoin(): Pass friendCode and puid parameters when constructing Player - handleRemoteJoin(): PlayerJoinData includes puid and friendCode - Crewmate role, passive ability - Client automatically displays Vitals UI after recognizing via SetRole RPC - No special server-side logic - Crewmate role, can use vents - Track ventUsesRemaining (from roleSettings.engineerVentUses / crewmateVentUses) - onVentUse(): Check and decrement usage count - Crewmate role, can shield alive players - Shield duration from guardianAngelProtectionDuration setting - Cooldown from guardianAngelCooldown setting - onAbilityUse(): Set protection state via PlayerControl - onFixedUpdate(): Drive cooldown timer - onDeath(): Remove current protection - Impostor role, can shapeshift into other players' appearances - Shift duration from shapeshifterDuration setting - Cooldown from shapeshifterCooldown setting - onAbilityUse(): Save original appearance → apply target appearance - revertTransform(): Restore original appearance → push PlayerInfo update - onFixedUpdate(): Drive shapeshift timer and cooldown - Add Scientist, Engineer, GuardianAngel, Shapeshifter to ROLE_CONSTRUCTORS - Add Crewmate to isCrewmateRole - Add Impostor to isImpostorRole - Add ScientistRole, EngineerRole, GuardianAngelRole, ShapeshifterRole exports - onDeath(): Trigger vanishWithAuth() RPC instead of just sending chat message - endVanish(): Trigger appearWithAuth() RPC + causeToDie for official death - onFixedUpdate(): Drive vanish timer and cooldown - Remove all setTimeout, use fixed update timers instead - onKill(): Poison delay, track poisoned players and remaining time via Map - onFixedUpdate(): Countdown dissolution, call causeToDie - dissolveTarget(): Execute dissolution kill - Remove setTimeout, use fixed update timers instead - onFixedUpdate(): Drive tracking duration, position update interval, cooldown - sendPositionUpdate(): Read target CustomNetworkTransform to get position - Remove setInterval, use fixed update instead - onFixedUpdate(): Drive alarm effect timer and cooldown - Remove setTimeout dependency - onFixedUpdate(): Drive cooldown timer - onGameStart(): Register player.murder listener to track kill records - Remove setInterval dependency - Among Us native /cmd chat command handler - Built-in commands: - /cmd help — Show available commands - /cmd list — List room players (with FriendCode) - /cmd kick — Kick a player - /cmd ban — Ban a player - /cmd room — Set room name - /cmd public — Make room public - /cmd private — Make room private - CmdContext: Supports reply (visible only to sender) and broadcast (entire room) - Player lookup: Supports fuzzy username matching and exact ClientID matching - Authentication requirement: kick/ban/room/public/private require isAuthenticated - Extensible: register() method for plugins to register custom commands - Add cmdHandler: CmdHandler field and instantiate it - handleRpcMessage(): Detect /cmd messages from non-authority players - Call cmdHandler.handleMessage() to process the command - Suppress broadcast (aligns with Impostor /cmd being host-only visible) - player.chat event: Skip /cmd messages (avoid duplicate processing with CmdHandler) --- changelog.txt | 180 ------------------ src/AuthCache.ts | 180 ++++++++++++++++++ src/Connection.ts | 21 ++ src/Matchmaker.ts | 192 +++++++++++++++++-- src/Room.ts | 60 +++++- src/WaterwayServer.ts | 23 +++ src/game/roles/DetectiveRole.ts | 49 +++-- src/game/roles/EngineerRole.ts | 62 ++++++ src/game/roles/GuardianAngelRole.ts | 117 ++++++++++++ src/game/roles/NoisemakerRole.ts | 74 +++++--- src/game/roles/PhantomRole.ts | 100 +++++----- src/game/roles/RoleManager.ts | 10 + src/game/roles/ScientistRole.ts | 33 ++++ src/game/roles/ShapeshifterRole.ts | 179 ++++++++++++++++++ src/game/roles/TrackerRole.ts | 92 +++++---- src/game/roles/ViperRole.ts | 73 +++---- src/game/roles/index.ts | 4 + src/handlers/CmdHandler.ts | 284 ++++++++++++++++++++++++++++ src/handlers/index.ts | 1 + 19 files changed, 1368 insertions(+), 366 deletions(-) delete mode 100644 changelog.txt create mode 100644 src/AuthCache.ts create mode 100644 src/game/roles/EngineerRole.ts create mode 100644 src/game/roles/GuardianAngelRole.ts create mode 100644 src/game/roles/ScientistRole.ts create mode 100644 src/game/roles/ShapeshifterRole.ts create mode 100644 src/handlers/CmdHandler.ts diff --git a/changelog.txt b/changelog.txt deleted file mode 100644 index 62bc603ff..000000000 --- a/changelog.txt +++ /dev/null @@ -1,180 +0,0 @@ -fix: game restart state corruption, room privacy inverted, missing QuickChat - -=== [FIX] Room Privacy Inverted (src/Room.ts) === - - finishJoiningGame() sent AlterGame(ChangePrivacy, 1) when room was Private - because the ternary mapped RoomPrivacy.Private(=1) -> value 1, but in the - Among Us protocol, value 1 means Public. - - Result: every newly-joined player received an initial AlterGame telling them - the room was public when it was actually private. Manual toggle by host - fixed it because handleAlterGameMessage() (correct path) was then used. - - Fix: change ternary from "RoomPrivacy.Private ? 1 : 0" to - "RoomPrivacy.Public ? 1 : 0" - -=== [FIX] Missing QuickChat in HTTP Matchmaker (src/Matchmaker.ts) === - - GameListingJson type was missing the QuickChat field that Among Us clients - read from the HTTP /api/games response to determine chat mode. - - getGameListing() now sets QuickChat from the host connection's chatMode. - - Without this, joining clients defaulted to QuickChatOnly display. - -=== [FIX] Game restart state corruption (src/Room.ts) === - - _reset() was not being called between games, causing Duplicate NetID - crash because lastNetId kept incrementing across games. - - handleStartGame() now calls _reset() when gameState === Ended. - - _reset() no longer clears this.players — lobby connections must persist. - - _reset() now resets: lastNetId→100000, networkedObjects, objectList, - roleManager, gameModeManager, ownershipGuards, messageStream, settings. - -=== [FIX] Missing Language field in UDP GameListing (src/WaterwayServer.ts) === - - UDP game listing now passes room.settings.keywords as Language to the - GameListing constructor (11th arg, cast to any for npm compat until - SkeldJS protocol update is published). - -feat: game options validation, Hide & Seek manager, role system, API events, matchmaker improvements, and config updates - -=== Game Options Validation (src/game/GameOptions.ts) [NEW] === - + GameOptionsValidator.validateGameSettings() — validates all game settings - + Numeric range checks: playerSpeed, killCooldown, vision, tasks, timers - + Enum checks: GameMap, GameKeyword, KillDistance, TaskBarMode, GameMode, - RulesPreset - + Dependency check: numEmergencies <= maxPlayers - + Role settings validation for all 9 roles - + Used in WaterwayServer.handleHostGameMessage() on room creation - + Used in Room constructor on player.syncsettings RPC - -=== Hide & Seek Game Mode Manager (src/game/modes/HideAndSeekManager.ts) [NEW] === - + Full lifecycle: Hiding -> Seeking -> FinalHide -> Ended - + Automatic seeker selection (configured playerId or random) - + Hider tracking with task-completion win condition - + Body reports and meetings disabled during gameplay - + Final hide phase: seeker speed boost, position ping mechanic - + Player disconnect handling: - - Seeker leaves -> hiders win - - All hiders leave -> seeker wins - + Integrated into Room.handleStartGame() and Room.handleRpcMessage() - + Clean teardown on destroy / game end - -=== Role System (src/game/roles/) [NEW] === - - [NEW] BaseRole.ts — server-side role base class - + Cooldown management (startCooldown, canUseAbility, isOnCooldown) - + Hooks: onGameStart, onKill, onTaskComplete, onDeath, onMeetingStart, - onAbilityUse, onFixedUpdate, onGameEnd - + roleType, teamType, isActive, currentCooldown, lastAbilityUse - - [NEW] NoisemakerRole.ts — crewmate, emits alert on task completion - + onTaskComplete(): broadcasts position reveal to nearby players - + Configurable alert duration and impostor visibility - - [NEW] PhantomRole.ts — impostor, vanishes on death - + onDeath(): enters invisible ghost state instead of dying - + Vanish duration + cooldown from role settings - + Auto-dies after vanish expires - - [NEW] TrackerRole.ts — crewmate, tracks a target - + onAbilityUse(target): starts periodic position updates - + Tracking duration, cooldown, and update delay from role settings - + Auto-stops after duration - - [NEW] DetectiveRole.ts — crewmate, inspects suspects - + onAbilityUse(target): checks kill count, reports suspicion level - + suspectLimit from role settings - + Tracks murder records during game - - [NEW] ViperRole.ts — impostor, delayed poison kills - + onKill(target): applies poison instead of instant kill - + Target dies after viperDissolveTime seconds - + All poisons cleared on Viper death or game end - - [NEW] RoleManager.ts — role assignment and lifecycle - + assignRoles(): reads roleChances from settings, rolls dice per player - + Respects maxPlayers limits and team constraints - + Routes game events (kill, task, death, meeting) to correct role - + Plugin-extensible via static registerRole() - -=== New API Events (src/api/events/) [NEW] === - - [NEW] room/HideAndSeekStart.ts — room.hideandseekstart - Fields: room, seekerPlayer, hiderPlayers, hideDuration - - [NEW] room/HiderCaught.ts — room.hidercaught - Fields: room, hiderPlayer, seekerPlayer, remainingHiders - - [NEW] room/HideAndSeekEnd.ts — room.hideandseekend - Fields: room, winner ("seekers"|"hiders"|"none"), reason - - [NEW] player/TaskStarted.ts — player.taskstarted - Fields: room, player, taskType, taskId - - [NEW] player/TaskProgress.ts — player.taskprogress - Fields: room, player, taskId, progress - - [NEW] player/TaskCompleted.ts — player.taskcompleted - Fields: room, player, taskType, taskId, isLongTask - - [NEW] player/LevelChanged.ts — player.levelchanged - Fields: room, player, oldLevel, newLevel - Emitted in Room.on("player.setlevel") handler - - [NEW] player/MurderFail.ts — player.murderfail - Fields: room, player (killer), target, reason, details? - Emitted from Room.on("player.checkmurder") when murder is invalid - - [MODIFY] events/room/index.ts — export HideAndSeekStart, HideAndSeekEnd, - HiderCaught - [NEW] events/player/index.ts — export all new player events - [MODIFY] events/index.ts — export player module - -=== HTTP Matchmaker (src/Matchmaker.ts) [MODIFY] === - - [NEW] GET / — server dashboard HTML page - Shows: room/player/connection/plugin counts, public room list table, - API endpoint reference. Dark theme, zero external CSS. - - [ENHANCE] GET /api/games/:id — now returns full room info - + Players array: clientId, username, color, hat, skin, visor, - isHost, isDead - + GameState, GameMode, Privacy strings - - [FIX] GET /api/filtertags — was stub returning [], now dynamic - + Scans active rooms for maps, languages, game modes - + Merges with configurable static filterTags from server config - + Returns FilterTagJson[] with Name, DisplayName, Type, Count - - [NEW] POST /api/games/:id/refresh — room metadata refresh - + Host can update privacy and room name - + Returns updated GameListingJson - -=== Config Updates (src/WaterwayServer.ts, bin/createDefaultConfig.ts) [MODIFY] === - - [ADD] GameListingConfig.filterTags - Type: { name: string; displayName: string; icon?: string }[] - Served by GET /api/filtertags as static filter buttons - - [ADD] RoomsConfig.allowedGameModes - Type: number[] — restrict which game modes rooms can use - Empty array = all modes allowed - - [ADD] RoomsConfig.defaultGameMode - Type: number — game mode for new rooms (default 1 = Normal) - - [ENHANCE] Room constructor applies enforceSettings from config on creation - Settings in enforceSettings cannot be overridden by host - - [MODIFY] bin/createDefaultConfig.ts — all new fields with defaults - -=== Room Integration (src/Room.ts) [MODIFY] === - - + gameModeManager property — active game mode handler (HideAndSeekManager) - + roleManager property — role assignment and lifecycle - + Validates game options on player.syncsettings (rejects invalid) - + Emits PlayerLevelChangedEvent on player.setlevel - + Emits PlayerTaskCompletedEvent on player.completetask - + Emits PlayerMurderFailEvent on player.checkmurder (when !isValid) - + Routes kill/task/death through roleManager - + Routes RPCs through gameModeManager - + Cleanup in _reset() and destroy() - -=== Verification === - - TypeScript compilation passes cleanly (tsc --noEmit exits 0) - - All events follow existing Waterway event patterns - - Role classes extend Waterway BaseRole (server-side), not SkeldJS BaseRole diff --git a/src/AuthCache.ts b/src/AuthCache.ts new file mode 100644 index 000000000..4d5b532ed --- /dev/null +++ b/src/AuthCache.ts @@ -0,0 +1,180 @@ +/** + * Cached authentication data for a user, stored after TCP /api/user call + * and retrieved during UDP handshake matching. + */ +export interface CachedAuth { + /** EOS ProductUserId extracted from the JWT token. */ + puid: string; + /** FriendCode in username#discriminator format, from Innersloth backend. */ + friendCode: string; + /** When this cache entry expires. */ + expiresAt: number; + /** The username from the /api/user request body. */ + username: string; + /** The client IP address at the time of the /api/user request. */ + ip: string; + /** The client version reported by the user. */ + clientVersion: number; +} + +/** + * In-memory cache that maps (IP + username) → CachedAuth. + * + * Used to associate a UDP connection (identified by IP + username from + * the 0x08 Hello handshake) with the authentication data obtained during + * the prior TCP /api/user call. + * + * Fallback matching strategy: + * 1. Match by IP + username (most accurate) + * 2. Match by username only (for NAT / IP changes) + * 3. Match by IP only (for same-machine scenarios) + */ +export class AuthCache { + /** Primary index: `${ip}:${username}` → CachedAuth */ + private byIpAndUsername = new Map(); + + /** Secondary index: username → CachedAuth (for IP-change fallback) */ + private byUsername = new Map(); + + /** Tertiary index: IP → CachedAuth[] (for username-change fallback) */ + private byIp = new Map(); + + /** Default cache TTL: 10 minutes */ + static readonly DEFAULT_TTL_MS = 10 * 60 * 1000; + + constructor(private ttlMs: number = AuthCache.DEFAULT_TTL_MS) {} + + /** + * Store an authentication entry. + */ + addAuth(ip: string, username: string, puid: string, friendCode: string, clientVersion: number): CachedAuth { + this.cleanExpired(); + + const entry: CachedAuth = { + puid, + friendCode, + expiresAt: Date.now() + this.ttlMs, + username, + ip, + clientVersion, + }; + + const ipUsernameKey = `${ip}:${username}`; + this.byIpAndUsername.set(ipUsernameKey, entry); + this.byUsername.set(username, entry); + + const ipEntries = this.byIp.get(ip); + if (ipEntries) { + // Replace existing entry for this username, or add new + const existingIdx = ipEntries.findIndex(e => e.username === username); + if (existingIdx >= 0) { + ipEntries[existingIdx] = entry; + } else { + ipEntries.push(entry); + } + } else { + this.byIp.set(ip, [entry]); + } + + return entry; + } + + /** + * Primary matching: IP + username. + */ + findByIpAndUsername(ip: string, username: string): CachedAuth | null { + this.cleanExpired(); + const key = `${ip}:${username}`; + const entry = this.byIpAndUsername.get(key); + if (entry && entry.expiresAt > Date.now()) { + return entry; + } + return null; + } + + /** + * Fallback 1: match by username only (IP may have changed). + */ + findByUsername(username: string): CachedAuth | null { + this.cleanExpired(); + const entry = this.byUsername.get(username); + if (entry && entry.expiresAt > Date.now()) { + return entry; + } + return null; + } + + /** + * Fallback 2: match by IP only (useful for same-machine scenarios). + * Returns the most recently added entry for this IP. + */ + findByIp(ip: string): CachedAuth | null { + this.cleanExpired(); + const entries = this.byIp.get(ip); + if (entries && entries.length > 0) { + // Return the most recent valid entry + const valid = entries.filter(e => e.expiresAt > Date.now()); + if (valid.length > 0) { + return valid.reduce((a, b) => a.expiresAt > b.expiresAt ? a : b); + } + } + return null; + } + + /** + * Best-effort matching: try all strategies in order. + */ + findBestMatch(ip: string, username: string): CachedAuth | null { + return this.findByIpAndUsername(ip, username) + || this.findByUsername(username) + || this.findByIp(ip); + } + + /** + * Remove expired entries from all indexes. + */ + cleanExpired(): void { + const now = Date.now(); + + for (const [key, entry] of this.byIpAndUsername) { + if (entry.expiresAt <= now) { + this.byIpAndUsername.delete(key); + this.byUsername.delete(entry.username); + + const ipEntries = this.byIp.get(entry.ip); + if (ipEntries) { + const idx = ipEntries.indexOf(entry); + if (idx >= 0) ipEntries.splice(idx, 1); + if (ipEntries.length === 0) this.byIp.delete(entry.ip); + } + } + } + } + + /** + * Remove a specific entry (e.g., after successful association). + */ + remove(ip: string, username: string): void { + const key = `${ip}:${username}`; + const entry = this.byIpAndUsername.get(key); + if (entry) { + this.byIpAndUsername.delete(key); + this.byUsername.delete(username); + + const ipEntries = this.byIp.get(ip); + if (ipEntries) { + const idx = ipEntries.indexOf(entry); + if (idx >= 0) ipEntries.splice(idx, 1); + if (ipEntries.length === 0) this.byIp.delete(ip); + } + } + } + + /** + * Get the number of active cache entries. + */ + get size(): number { + this.cleanExpired(); + return this.byIpAndUsername.size; + } +} diff --git a/src/Connection.ts b/src/Connection.ts index 49b840432..44d1bb3d7 100644 --- a/src/Connection.ts +++ b/src/Connection.ts @@ -130,6 +130,21 @@ export class Connection { */ playerLevel: number; + /** + * EOS ProductUserId — set after UDP handshake matching against AuthCache. + */ + puid: string; + + /** + * FriendCode in username#discriminator format — from Innersloth backend. + */ + friendCode: string; + + /** + * Whether this connection has been matched to a prior TCP /api/user auth. + */ + isAuthenticated: boolean; + /** * The last nonce that was received by this client. * @@ -193,6 +208,9 @@ export class Connection { this.clientVersion = new Version(2021, 11, 9); this.platform = new PlatformSpecificData(Platform.Unknown, "Unknown"); this.playerLevel = 0; + this.puid = ""; + this.friendCode = ""; + this.isAuthenticated = false; this.nextExpectedNonce = 0; this._incrNonce = 0; @@ -354,6 +372,9 @@ export class Connection { this.clientVersion = new Version(2021, 11, 9); this.platform = new PlatformSpecificData(Platform.Unknown, "Unknown"); this.playerLevel = 0; + this.puid = ""; + this.friendCode = ""; + this.isAuthenticated = false; this.server.removeConnection(this); diff --git a/src/Matchmaker.ts b/src/Matchmaker.ts index 988d3b269..90f1af077 100644 --- a/src/Matchmaker.ts +++ b/src/Matchmaker.ts @@ -2,6 +2,7 @@ import * as chalk from "chalk"; import * as Koa from "koa"; import koaBody from "koa-body"; import * as http from "http"; +import * as https from "https"; import * as crypto from "crypto"; import * as KoaRouter from "@koa/router"; @@ -12,6 +13,7 @@ import { DisconnectReason, Filters, GameKeyword, GameMap, GameMode, GameState, P import { WaterwayServer } from "./WaterwayServer"; import { Room, RoomPrivacy, logMaps } from "./Room"; import { Logger } from "./Logger"; +import { AuthCache } from "./AuthCache"; export type GameListingJson = { IP: number; @@ -249,9 +251,117 @@ export class Matchmaker { httpServer: http.Server|undefined; privateKey: Buffer; + /** AuthCache shared with WaterwayServer for UDP handshake matching. */ + authCache: AuthCache; + constructor(protected readonly server: WaterwayServer) { this.logger = new Logger(chalk.redBright("Http"), this.server.vorpal); this.privateKey = crypto.randomBytes(128); + this.authCache = new AuthCache(); + } + + /** + * Decode a JWT token and extract the PUID from the `sub` claim. + * Does NOT verify the signature — we only need the payload. + * JWT format: base64url(header).base64url(payload).base64url(signature) + */ + extractPuidFromJwt(eosToken: string): string | null { + try { + const parts = eosToken.split("."); + if (parts.length !== 3) return null; + + // Decode the payload (second part) + const payload = Buffer.from(parts[1], "base64url").toString("utf8"); + const json = JSON.parse(payload); + + // Among Us EOS tokens use `sub` for PUID + return json.sub || json.puid || null; + } catch { + return null; + } + } + + /** + * Fetch the FriendCode for a PUID from the Innersloth backend. + * Uses the EOS Bearer token for authorization. + */ + async fetchFriendCodeFromBackend(eosToken: string, puid: string): Promise { + return new Promise((resolve) => { + const url = "https://backend.innersloth.com/api/user/username"; + + const req = https.get(url, { + headers: { + "Authorization": "Bearer " + eosToken, + "User-Agent": "UnityPlayer/2022.3.44f1 (UnityWebRequest/1.0, libcurl/7.84.0-DEV)", + "X-Unity-Version": "2022.3.44f1", + "Accept": "application/vnd.api+json", + }, + timeout: 15000, + }, (res) => { + let data = ""; + res.on("data", (chunk: Buffer) => data += chunk.toString()); + res.on("end", () => { + try { + if (res.statusCode !== 200) { + this.logger.warn( + "Innersloth backend returned %s for PUID %s", + res.statusCode, puid + ); + resolve(null); + return; + } + + const doc = JSON.parse(data); + + // JSON:API format: { data: { attributes: { username, discriminator } } } + // Also support flat format: { username, discriminator } + let attrs: any; + if (doc.data && doc.data.attributes) { + attrs = doc.data.attributes; + } else { + attrs = doc; + } + + const username = attrs.username; + const discriminator = attrs.discriminator; + + if (username && discriminator) { + resolve(`${username}#${discriminator}`); + } else { + this.logger.warn( + "Innersloth response missing username/discriminator for PUID=%s. Response: %s", + puid, data.substring(0, 200) + ); + resolve(null); + } + } catch (e) { + this.logger.error("Error parsing Innersloth response: %s", e); + resolve(null); + } + }); + }); + + req.on("error", (e) => { + this.logger.error("Error fetching FriendCode from backend: %s", e.message); + resolve(null); + }); + + req.on("timeout", () => { + req.destroy(); + this.logger.warn("Timeout fetching FriendCode from backend for PUID %s", puid); + resolve(null); + }); + }); + } + + /** + * Generate a fallback FriendCode from a PUID hash. + * Ensures the same PUID always gets the same fallback code. + */ + generateFallbackFriendCode(puid: string): string { + const hash = crypto.createHash("sha256").update(puid).digest(); + const discriminator = hash.readUInt16LE(0) % 10000; + return `Player#${discriminator.toString().padStart(4, "0")}`; } get port() { @@ -651,12 +761,6 @@ export class Matchmaker { return; } - if (typeof body.Puid !== "string") { - this.logger.warn("Client failed to get a matchmaker token: No 'Puid' provided in body"); - ctx.status = 400; - return; - } - if (typeof body.Username !== "string") { this.logger.warn("Client failed to get a matchmaker token: No 'Username' provided in body"); ctx.status = 400; @@ -683,14 +787,78 @@ export class Matchmaker { return; } - // todo: record matchmaking tokens used - if (this.server.config.logging.hideSensitiveInfo) { - this.logger.info("Client %s got a matchmaker token", chalk.blue(body.Username)); - } else { - this.logger.info("Client %s (%s) got a matchmaker token", chalk.blue(body.Username), chalk.grey(body.Puid)); + // ── Extract PUID ── + // Priority: 1) Authorization header (EOS JWT Bearer token) + // 2) body.Puid field (legacy / direct) + let puid: string | null = null; + + const authHeader = ctx.headers.authorization; + if (authHeader && authHeader.startsWith("Bearer ")) { + const eosToken = authHeader.substring("Bearer ".length); + puid = this.extractPuidFromJwt(eosToken); + + if (puid) { + // Fetch FriendCode from Innersloth backend + let friendCode = await this.fetchFriendCodeFromBackend(eosToken, puid); + + if (!friendCode) { + friendCode = this.generateFallbackFriendCode(puid); + this.logger.info("Using fallback FriendCode for %s: %s", chalk.blue(body.Username), friendCode); + } else { + this.logger.info("Got FriendCode from backend for %s: %s", chalk.blue(body.Username), friendCode); + } + + // Get client IP (support reverse proxy headers) + let clientIp = ctx.socket.remoteAddress || "127.0.0.1"; + const xRealIp = ctx.headers["x-real-ip"] as string; + const xForwardedFor = ctx.headers["x-forwarded-for"] as string; + if (xRealIp) { + clientIp = xRealIp; + } else if (xForwardedFor) { + clientIp = xForwardedFor.split(",")[0].trim(); + } + // Normalize IPv4-mapped IPv6 + if (clientIp.startsWith("::ffff:")) { + clientIp = clientIp.substring(7); + } + + // Store in AuthCache for later UDP handshake matching + this.authCache.addAuth(clientIp, body.Username, puid, friendCode, body.ClientVersion); + + if (this.server.config.logging.hideSensitiveInfo) { + this.logger.info("Client %s authenticated (PUID=XXXX, FriendCode=%s)", + chalk.blue(body.Username), friendCode); + } else { + this.logger.info("Client %s authenticated (PUID=%s, FriendCode=%s, IP=%s)", + chalk.blue(body.Username), puid, friendCode, clientIp); + } + } else { + this.logger.warn("Could not extract PUID from JWT for %s", chalk.blue(body.Username)); + } + } else if (typeof body.Puid === "string") { + // Legacy path: PUID provided directly in body + puid = body.Puid; + + let clientIp = ctx.socket.remoteAddress || "127.0.0.1"; + const xRealIp = ctx.headers["x-real-ip"] as string; + if (xRealIp) clientIp = xRealIp; + if (clientIp.startsWith("::ffff:")) clientIp = clientIp.substring(7); + + const friendCode = this.generateFallbackFriendCode(puid!); + this.authCache.addAuth(clientIp, body.Username, puid!, friendCode, body.ClientVersion); + + this.logger.info("Client %s got token via legacy PUID path (FriendCode=%s)", + chalk.blue(body.Username), friendCode); + } + + if (!puid) { + this.logger.warn("Client %s failed to get a matchmaker token: No PUID available (no Authorization header or body.Puid)", chalk.blue(body.Username)); + ctx.status = 400; + ctx.body = JSON.stringify({ error: "No PUID available" }); + return; } - const mmToken = this.generateMatchmakerToken(body.Puid, body.ClientVersion); + const mmToken = this.generateMatchmakerToken(puid, body.ClientVersion); ctx.status = 200; ctx.body = mmToken; }); diff --git a/src/Room.ts b/src/Room.ts index 988c7f9da..1b59a737e 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -19,6 +19,7 @@ import { ReadyMessage, ReliablePacket, RemoveGameMessage, + ReportDeadBodyMessage, RpcMessage, S2CHostGameMessage, S2CJoinGameMessage, @@ -109,6 +110,7 @@ import { RoomPlugin, WorkerPlugin } from "./handlers"; +import { CmdHandler } from "./handlers/CmdHandler"; import { UnknownComponent } from "./components"; @@ -344,6 +346,11 @@ export class Room extends StatefulRoom { */ roleManager: RoleManager; + /** + * Handler for Among Us vanilla /cmd chat commands. + */ + cmdHandler: CmdHandler; + protected roomNameOverride: string; protected eventTargets: EventTarget[]; @@ -387,6 +394,7 @@ export class Room extends StatefulRoom { this.eventTargets = []; this.gameModeManager = null; this.roleManager = new RoleManager(this); + this.cmdHandler = new CmdHandler(this); this.lastNetId = 100000; @@ -423,6 +431,13 @@ export class Room extends StatefulRoom { this.logger.info("%s sent message: %s", ev.player, chalk.red(ev.chatMessage)); + // /cmd messages are handled by the RPC intercept in handleRpcMessage + // (suppressed broadcast + CmdHandler processing). Skip them here. + if (ev.chatMessage.startsWith("/cmd")) { + // Still log, but don't process through regular chat command handler + return; + } + const prefix = typeof this.config.chatCommands === "object" ? this.config.chatCommands.prefix || "/" : "/"; @@ -494,6 +509,7 @@ export class Room extends StatefulRoom { } else { this.logger.info("Meeting started (%s's body was reported)", ev.body); } + this.roleManager.handleMeetingStart(); }); this.on("player.completetask", async ev => { @@ -666,6 +682,16 @@ export class Room extends StatefulRoom { this.playerJoinedFlag = true; } + // Tick role cooldowns and timers + if (this.gameState === GameState.Started) { + this.roleManager.handleFixedUpdate(); + } + + // Tick game mode-specific logic + if (this.gameModeManager && typeof (this.gameModeManager as any).handleFixedUpdate === "function") { + (this.gameModeManager as any).handleFixedUpdate(); + } + await super.processFixedUpdate(); } @@ -853,6 +879,26 @@ export class Room extends StatefulRoom { } else { await component.handleRemoteCall(message.child); } + + // In SaaH (authoritative) mode, ReportDeadBody is handled by the + // server itself — it starts the meeting and broadcasts StartMeeting. + // Suppress the original ReportDeadBody broadcast to prevent clients + // from showing a duplicate report screen / triggering conflicting meeting starts. + if (this.isAuthoritative && message.child instanceof ReportDeadBodyMessage) { + return true; + } + + // In SaaH mode, /cmd messages from non-host players should only + // be seen by the server (host). Suppress broadcast to other clients. + // The server processes the command and responds privately to the caller. + if (this.isAuthoritative && message.child instanceof SendChatMessage) { + const msg = message.child.message; + if (msg.startsWith("/cmd") && senderPlayer.clientId !== this.authorityId) { + // Route to cmd handler — don't broadcast to other players + await this.cmdHandler.handleMessage(senderPlayer, msg.substring(5)); + return true; // Suppress broadcast + } + } } catch (e) { this.logger.error("Could not process remote procedure call from player %s for component net id %s, %s: %s", senderPlayer, component.netId, SpawnType[component.spawnType] || "Unknown", e); @@ -1388,7 +1434,15 @@ export class Room extends StatefulRoom { if (cachedPlayer) return cachedPlayer; - const player = new Player(this, joinInfo.clientId, joinInfo.playerName, joinInfo.platform, joinInfo.playerLevel); + const player = new Player( + this, + joinInfo.clientId, + joinInfo.playerName, + joinInfo.platform, + joinInfo.playerLevel, + joinInfo.friendCode || "", + joinInfo.puid || "" + ); this.players.set(joinInfo.clientId, player); return player; @@ -1432,8 +1486,8 @@ export class Room extends StatefulRoom { joiningClient.username, joiningClient.platform, joiningClient.playerLevel, - "", // todo: combine worker with matchmaker - "" + joiningClient.puid || "", + joiningClient.friendCode || "" ); const joiningPlayer = await this.handleJoin(joinData); diff --git a/src/WaterwayServer.ts b/src/WaterwayServer.ts index 0d5e69500..f32e94c6c 100644 --- a/src/WaterwayServer.ts +++ b/src/WaterwayServer.ts @@ -1492,6 +1492,29 @@ export class WaterwayServer extends EventEmitter { sender.platform = helloPacket.platform; sender.playerLevel = 0; + // ── Auth matching: associate UDP connection with prior TCP /api/user auth ── + // Strategy: IP + username (primary) → username only → IP only + const clientIp = sender.remoteInfo.address; + if (this.matchmaker && this.matchmaker.authCache) { + const cachedAuth = this.matchmaker.authCache.findBestMatch(clientIp, sender.username); + if (cachedAuth) { + sender.puid = cachedAuth.puid; + sender.friendCode = cachedAuth.friendCode; + sender.isAuthenticated = true; + + if (this.config.logging.hideSensitiveInfo) { + this.logger.info("%s authenticated (FriendCode=%s)", + sender, cachedAuth.friendCode); + } else { + this.logger.info("%s authenticated (PUID=%s, FriendCode=%s)", + sender, cachedAuth.puid, cachedAuth.friendCode); + } + + // Remove from cache after successful association (single-use) + this.matchmaker.authCache.remove(clientIp, sender.username); + } + } + if (!this.isVersionAccepted(sender.clientVersion)) { this.logger.warn("%s connected with invalid client version: %s", sender, sender.clientVersion.toString()); diff --git a/src/game/roles/DetectiveRole.ts b/src/game/roles/DetectiveRole.ts index 9d74168f8..ca5e5380a 100644 --- a/src/game/roles/DetectiveRole.ts +++ b/src/game/roles/DetectiveRole.ts @@ -23,8 +23,14 @@ export class DetectiveRole extends BaseRole { /** Records of player kills tracked by the detective. */ private _murderRecords: Map = new Map(); + /** Remaining cooldown before next inspection (seconds). */ + private _cooldownTimer: number = 0; + + /** Whether murder listener is registered. */ + private _murderListenerRegistered: boolean = false; + get suspectLimit(): number { - return this.room.settings.roleSettings.detectiveSuspectLimit || 3; + return (this.room.settings.roleSettings as any).detectiveSuspectLimit || 3; } onGameStart(): void { @@ -32,18 +38,22 @@ export class DetectiveRole extends BaseRole { this.inspectedCount = 0; this.inspectedPlayers.clear(); this._murderRecords.clear(); + this._cooldownTimer = 0; + + // Listen for murders to track who kills + if (!this._murderListenerRegistered) { + this._murderListenerRegistered = true; + this.room.on("player.murder", (ev: any) => { + if (ev.player && ev.victim) { + const killerId = ev.player.clientId; + const currentCount = this._murderRecords.get(killerId) || 0; + this._murderRecords.set(killerId, currentCount + 1); + } + }); + } this.room.logger.info("%s is the Detective (suspect limit: %s)", this.player, this.suspectLimit); - - // Listen for murders to track who kills - this.room.on("player.murder", (ev: any) => { - if (ev.player && ev.target) { - const killerId = ev.player.clientId; - const currentCount = this._murderRecords.get(killerId) || 0; - this._murderRecords.set(killerId, currentCount + 1); - } - }); } /** @@ -61,12 +71,13 @@ export class DetectiveRole extends BaseRole { } if (this.inspectedCount >= this.suspectLimit) { - this.room.logger.warn("%s (Detective) has reached the suspect limit (%s)", + this.room.logger.warn("%s (Detective) reached the suspect limit (%s)", this.player, this.suspectLimit); return false; } - // Perform the inspection + if (!this.canUseAbility()) return false; + this.inspectedCount++; this.inspectedPlayers.add(target.clientId); @@ -77,7 +88,7 @@ export class DetectiveRole extends BaseRole { this.room.logger.info("%s (Detective) inspected %s: %s kills, isImpostor=%s", this.player, target, kills, isImpostor); - // Send the inspection result to the detective + // Send inspection result let resultMessage: string; if (kills > 0) { resultMessage = `Detective report: ${target.username || "Player"} has committed ${kills} murder(s)! Highly suspicious!`; @@ -92,13 +103,19 @@ export class DetectiveRole extends BaseRole { return true; } - /** - * Get the number of remaining inspections available. - */ get remainingInspections(): number { return Math.max(0, this.suspectLimit - this.inspectedCount); } + onFixedUpdate(): void { + if (this._cooldownTimer > 0) { + this._cooldownTimer -= 0.1; + if (this._cooldownTimer <= 0) { + this._cooldownTimer = 0; + } + } + } + onDeath(): boolean { this.isActive = false; return true; diff --git a/src/game/roles/EngineerRole.ts b/src/game/roles/EngineerRole.ts new file mode 100644 index 000000000..4ad90283d --- /dev/null +++ b/src/game/roles/EngineerRole.ts @@ -0,0 +1,62 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; + +/** + * Engineer Role + * + * A Crewmate role that can use vents (normally an Impostor-only ability). + * The Engineer has a limited number of vent uses per game, defined in + * the role settings (crewmateVentUses). + * + * Vent usage (EnterVent/ExitVent RPCs) is handled by SkeldJS PlayerPhysics. + * This role tracks the remaining vent uses and blocks venting when exhausted. + */ +export class EngineerRole extends BaseRole { + roleType = RoleType.Engineer; + teamType = RoleTeamType.Crewmate; + + /** Number of vent uses remaining for this Engineer. */ + ventUsesRemaining: number = 0; + + /** Maximum vent uses from role settings. */ + get maxVentUses(): number { + return (this.room.settings.roleSettings as any).engineerVentUses + ?? this.room.settings.crewmateVentUses + ?? 10; + } + + onGameStart(): void { + this.isActive = true; + this.ventUsesRemaining = this.maxVentUses; + this.room.logger.info("%s is the Engineer (vent uses: %s)", + this.player, this.ventUsesRemaining); + } + + /** + * Called when the Engineer uses a vent. + * Returns whether the vent usage is allowed. + */ + onVentUse(): boolean { + if (!this.isActive) return false; + + if (this.ventUsesRemaining <= 0) { + this.room.logger.info("%s (Engineer) has no vent uses remaining", this.player); + return false; + } + + this.ventUsesRemaining--; + this.room.logger.debug("%s (Engineer) used vent, %s remaining", + this.player, this.ventUsesRemaining); + return true; + } + + onDeath(): boolean { + this.isActive = false; + return true; + } + + onGameEnd(): void { + this.isActive = false; + } +} diff --git a/src/game/roles/GuardianAngelRole.ts b/src/game/roles/GuardianAngelRole.ts new file mode 100644 index 000000000..5d3840ee5 --- /dev/null +++ b/src/game/roles/GuardianAngelRole.ts @@ -0,0 +1,117 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; + +/** + * GuardianAngel Role + * + * A Crewmate role that can cast a protective shield on a living player. + * The shield blocks one kill attempt and then dissipates. + * + * RPC flow (handled by SkeldJS PlayerControl): + * 1. GA selects target → client sends CheckProtect RPC (48) to host + * 2. Host/server broadcasts ProtectPlayer RPC (45) → applies shield + * 3. Kill attempt on shielded target → MurderResultFlags.FailedProtected + * + * The shield duration and cooldown come from role settings. + * Protection removal (on timeout or successful block) is handled + * by PlayerControl.processFixedUpdate. + */ +export class GuardianAngelRole extends BaseRole { + roleType = RoleType.GuardianAngel; + teamType = RoleTeamType.Crewmate; + + /** Remaining cooldown before GA can protect again (seconds). */ + private _cooldownTimer: number = 0; + + /** The player currently protected by this GA, if any. */ + protectedTarget: Player | null = null; + + get cooldown(): number { + // Overrides from role settings if available + const settings = this.room.settings.roleSettings as any; + return settings.guardianAngelCooldown ?? 35; + } + + get protectionDuration(): number { + const settings = this.room.settings.roleSettings as any; + return settings.guardianAngelProtectionDuration ?? 10; + } + + onGameStart(): void { + this.isActive = true; + this._cooldownTimer = 0; + this.protectedTarget = null; + this.room.logger.info("%s is the GuardianAngel (cooldown: %ss, duration: %ss)", + this.player, this.cooldown, this.protectionDuration); + } + + /** + * Attempt to protect a target player. + * Called when the GA uses their ability. + */ + onAbilityUse(target?: Player): boolean { + if (!target) { + this.room.logger.warn("%s (GA) attempted to protect but no target specified", this.player); + return false; + } + + if (!this.canUseAbility()) { + this.room.logger.warn("%s (GA) ability on cooldown (%ss remaining)", + this.player, Math.ceil(this._cooldownTimer)); + return false; + } + + const targetInfo = target.getPlayerInfo(); + if (targetInfo?.isDead) { + this.room.logger.warn("%s (GA) attempted to protect dead player %s", this.player, target); + return false; + } + + if (target.clientId === this.player.clientId) { + this.room.logger.warn("%s (GA) attempted to protect themselves", this.player); + return false; + } + + // Apply protection via PlayerControl + if (target.characterControl) { + target.characterControl.protectedByGuardian = true; + (target.characterControl as any)._protectedByGuardianTime = this.protectionDuration; + target.characterControl.guardianProtector = this.player; + } + + this.protectedTarget = target; + this.startCooldown(this.cooldown * 1000); + this._cooldownTimer = this.cooldown; + + this.room.logger.info("%s (GA) protected %s for %ss", + this.player, target, this.protectionDuration); + + return true; + } + + onFixedUpdate(): void { + if (this._cooldownTimer > 0) { + this._cooldownTimer -= 0.1; // Fixed update tick delta + if (this._cooldownTimer <= 0) { + this._cooldownTimer = 0; + } + } + } + + onDeath(): boolean { + // Remove protection when GA dies + if (this.protectedTarget?.characterControl) { + this.protectedTarget.characterControl.protectedByGuardian = false; + this.protectedTarget.characterControl.guardianProtector = undefined; + } + this.protectedTarget = null; + this.isActive = false; + return true; + } + + onGameEnd(): void { + this.protectedTarget = null; + this.isActive = false; + } +} diff --git a/src/game/roles/NoisemakerRole.ts b/src/game/roles/NoisemakerRole.ts index 568252552..ea1beaf17 100644 --- a/src/game/roles/NoisemakerRole.ts +++ b/src/game/roles/NoisemakerRole.ts @@ -14,71 +14,83 @@ export class NoisemakerRole extends BaseRole { roleType = RoleType.Noisemaker; teamType = RoleTeamType.Crewmate; - /** Duration of the alert in seconds. */ + /** Remaining alert effect time (seconds). */ + private _alertTimer: number = 0; + + /** Remaining cooldown before next alert (seconds). */ + private _cooldownTimer: number = 0; + get alertDuration(): number { - return this.room.settings.roleSettings.noisemakerAlertDuration || 10; + return (this.room.settings.roleSettings as any).noisemakerAlertDuration || 10; } /** Whether impostors also see the alert. */ get impostorAlert(): boolean { - return this.room.settings.roleSettings.noisemakerImpostorAlert !== false; + return (this.room.settings.roleSettings as any).noisemakerImpostorAlert !== false; } onGameStart(): void { this.isActive = true; + this._alertTimer = 0; + this._cooldownTimer = 0; this.room.logger.info("%s is the Noisemaker (alert duration: %ss, impostor alert: %s)", this.player, this.alertDuration, this.impostorAlert); } - onTaskComplete(taskType: number, taskId: number): void { + onTaskComplete(_taskType: number, _taskId: number): void { if (!this.isActive) return; + if (this._cooldownTimer > 0) return; - this.room.logger.info("%s (Noisemaker) completed task %s, triggering alert!", - this.player, taskId); - - // Broadcast the alert to nearby players + this.room.logger.info("%s (Noisemaker) completed task, triggering alert!", this.player); this.triggerAlert(); } /** * Trigger the noise alert, revealing this player's position. + * The Among Us client shows a map-wide arrow/indicator when a Noisemaker + * triggers an alert. */ private triggerAlert(): void { const characterControl = this.player.characterControl; if (!characterControl) return; - // The alert is broadcast to players based on proximity - // In the Among Us protocol, this is typically done by sending - // a position snap/sync to relevant players - - // Determine who should see the alert - const recipients: Player[] = []; - - for (const [, otherPlayer] of this.room.players) { - if (otherPlayer.clientId === this.player.clientId) continue; + // Send alert chat message + this.room.sendChat( + `${this.player.username || "Someone"} triggered a Noisemaker alert!` + ); - const otherInfo = otherPlayer.getPlayerInfo(); - if (!otherInfo) continue; + // Start alert effect timer + this._alertTimer = this.alertDuration; + this._cooldownTimer = this.alertDuration; - // Impostors can see if configured, crewmates always see - if (otherInfo.isImpostor && !this.impostorAlert) continue; + this.room.logger.debug("Noisemaker alert active for %ss", this.alertDuration); + } - recipients.push(otherPlayer); + onFixedUpdate(): void { + if (this._alertTimer > 0) { + this._alertTimer -= 0.1; + if (this._alertTimer <= 0) { + this._alertTimer = 0; + this.room.logger.debug("%s Noisemaker alert expired", this.player); + } } - // Send a chat message to indicate the alert - this.room.sendChat( - `🔔 Noisemaker alert! ${this.player.username || "Someone"} revealed their position!` - ); - - this.room.logger.debug("Noisemaker alert sent to %s players", recipients.length); - - // Start cooldown after alert - this.startCooldown(this.alertDuration * 1000); + if (this._cooldownTimer > 0) { + this._cooldownTimer -= 0.1; + if (this._cooldownTimer <= 0) { + this._cooldownTimer = 0; + } + } } onDeath(): boolean { + this._alertTimer = 0; this.isActive = false; return true; } + + onGameEnd(): void { + this._alertTimer = 0; + this.isActive = false; + } } diff --git a/src/game/roles/PhantomRole.ts b/src/game/roles/PhantomRole.ts index 4f547af06..af17b6941 100644 --- a/src/game/roles/PhantomRole.ts +++ b/src/game/roles/PhantomRole.ts @@ -9,6 +9,14 @@ import { BaseRole } from "./BaseRole"; * "vanished" ghost state where they can continue moving invisibly for a * limited duration. After the duration expires, they properly die. * + * RPC flow (handled by SkeldJS PlayerControl): + * 1. Phantom dies → client sends CheckVanish RPC (62) + * 2. Host/server validates → broadcasts StartVanish RPC (63) + * 3. Client shows vanish animation, Phantom is invisible + * 4. After duration, Phantom sends CheckAppear RPC (64) + * 5. Host/server validates → broadcasts StartAppear RPC (65) + * 6. Phantom properly dies + * * This is an Impostor role. */ export class PhantomRole extends BaseRole { @@ -18,54 +26,47 @@ export class PhantomRole extends BaseRole { /** Whether the phantom is currently in the vanished state. */ isVanished: boolean = false; - /** Timer for the vanish duration. */ - private _vanishTimer: NodeJS.Timeout | null = null; + /** Remaining vanish duration (seconds). */ + private _vanishTimer: number = 0; - /** Timer for the cooldown between vanish uses. */ - private _cooldownTimer: NodeJS.Timeout | null = null; + /** Remaining cooldown before vanish can be used again (seconds). */ + private _cooldownTimer: number = 0; get cooldown(): number { - return this.room.settings.roleSettings.phantomCooldown || 15; + return (this.room.settings.roleSettings as any).phantomCooldown || 15; } get duration(): number { - return this.room.settings.roleSettings.phantomDuration || 30; + return (this.room.settings.roleSettings as any).phantomDuration || 30; } onGameStart(): void { this.isActive = true; + this.isVanished = false; + this._vanishTimer = 0; + this._cooldownTimer = 0; this.room.logger.info("%s is the Phantom (duration: %ss, cooldown: %ss)", this.player, this.duration, this.cooldown); } /** * The Phantom doesn't die immediately. Instead, they enter a vanished state. + * Return false to prevent normal death processing. */ onDeath(): boolean { - if (!this.isActive) return true; // Normal death + if (!this.isActive) return true; // Enter vanished state instead of dying this.isVanished = true; + this._vanishTimer = this.duration; + this.room.logger.info("%s (Phantom) entered vanished state for %ss", this.player, this.duration); - // Notify the player - this.room.sendChat( - `${this.player.username || "Someone"} has vanished into the shadows...`, - { targets: [...this.room.players.values()] } - ); - - // Set a timer to properly die after the duration - this._vanishTimer = setTimeout(() => { - this.endVanish(); - }, this.duration * 1000); - - // Disable the ability until cooldown - this.isActive = false; - this._cooldownTimer = setTimeout(() => { - this.isActive = true; - this.room.logger.debug("%s Phantom ability ready again", this.player); - }, this.cooldown * 1000); + // Trigger vanish via the proper RPC flow + if (this.player.characterControl) { + this.player.characterControl.vanishWithAuth(); + } return false; // Prevent normal death } @@ -73,38 +74,49 @@ export class PhantomRole extends BaseRole { /** * End the vanished state and properly kill the phantom. */ - private endVanish(): void { + endVanish(): void { if (!this.isVanished) return; this.isVanished = false; - this.room.logger.info("%s (Phantom) vanished state ended", this.player); + this._vanishTimer = 0; + + // Trigger appear via the proper RPC flow + if (this.player.characterControl) { + this.player.characterControl.appearWithAuth(true); + } - // Clear timer - if (this._vanishTimer) { - clearTimeout(this._vanishTimer); - this._vanishTimer = null; + // Now actually die + const playerInfo = this.player.getPlayerInfo(); + if (playerInfo && !playerInfo.isDead) { + this.player.characterControl?.causeToDie("exiled"); } - // The player actually dies now via the normal game engine mechanics + this._cooldownTimer = this.cooldown; + this.isActive = false; + + this.room.logger.info("%s (Phantom) vanished state ended, now dead", this.player); } - /** - * Force-end the vanish state (e.g., game ends). - */ - forceEndVanish(): void { - if (this._vanishTimer) { - clearTimeout(this._vanishTimer); - this._vanishTimer = null; + onFixedUpdate(): void { + if (this.isVanished && this._vanishTimer > 0) { + this._vanishTimer -= 0.1; + if (this._vanishTimer <= 0) { + this.endVanish(); + } + } + + if (!this.isVanished && this._cooldownTimer > 0) { + this._cooldownTimer -= 0.1; + if (this._cooldownTimer <= 0) { + this._cooldownTimer = 0; + this.isActive = true; + this.room.logger.debug("%s Phantom ability ready again", this.player); + } } - this.isVanished = false; } onGameEnd(): void { - this.forceEndVanish(); - if (this._cooldownTimer) { - clearTimeout(this._cooldownTimer); - this._cooldownTimer = null; - } + this.isVanished = false; this.isActive = false; } } diff --git a/src/game/roles/RoleManager.ts b/src/game/roles/RoleManager.ts index caf71927e..5b5697e50 100644 --- a/src/game/roles/RoleManager.ts +++ b/src/game/roles/RoleManager.ts @@ -6,11 +6,19 @@ import { PhantomRole } from "./PhantomRole"; import { TrackerRole } from "./TrackerRole"; import { DetectiveRole } from "./DetectiveRole"; import { ViperRole } from "./ViperRole"; +import { ScientistRole } from "./ScientistRole"; +import { EngineerRole } from "./EngineerRole"; +import { GuardianAngelRole } from "./GuardianAngelRole"; +import { ShapeshifterRole } from "./ShapeshifterRole"; /** * Maps role types to their implementing classes. */ const ROLE_CONSTRUCTORS: Partial) => BaseRole>> = { + [RoleType.Scientist]: ScientistRole, + [RoleType.Engineer]: EngineerRole, + [RoleType.GuardianAngel]: GuardianAngelRole, + [RoleType.Shapeshifter]: ShapeshifterRole, [RoleType.Noisemaker]: NoisemakerRole, [RoleType.Phantom]: PhantomRole, [RoleType.Tracker]: TrackerRole, @@ -69,6 +77,7 @@ export class RoleManager { const playerInfo = player.getPlayerInfo(); const isImpostor = playerInfo?.isImpostor || false; const isCrewmateRole = [ + RoleType.Crewmate, RoleType.Scientist, RoleType.Engineer, RoleType.GuardianAngel, @@ -78,6 +87,7 @@ export class RoleManager { ].includes(roleType); const isImpostorRole = [ + RoleType.Impostor, RoleType.Shapeshifter, RoleType.Phantom, RoleType.Viper, diff --git a/src/game/roles/ScientistRole.ts b/src/game/roles/ScientistRole.ts new file mode 100644 index 000000000..87ee70431 --- /dev/null +++ b/src/game/roles/ScientistRole.ts @@ -0,0 +1,33 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; + +/** + * Scientist Role + * + * A Crewmate role that has access to the Vitals monitor at all times. + * In the Among Us client, Vitals shows the alive/dead status of all players. + * + * This is a passive ability — the client handles the Vitals UI display + * once it knows the player is a Scientist (via SetRole RPC). + * No additional server-side RPC handling is needed. + */ +export class ScientistRole extends BaseRole { + roleType = RoleType.Scientist; + teamType = RoleTeamType.Crewmate; + + onGameStart(): void { + this.isActive = true; + this.room.logger.info("%s is the Scientist (passive: Vitals access)", + this.player); + } + + onDeath(): boolean { + this.isActive = false; + return true; + } + + onGameEnd(): void { + this.isActive = false; + } +} diff --git a/src/game/roles/ShapeshifterRole.ts b/src/game/roles/ShapeshifterRole.ts new file mode 100644 index 000000000..b8924d5cb --- /dev/null +++ b/src/game/roles/ShapeshifterRole.ts @@ -0,0 +1,179 @@ +import { Player, RoleType, RoleTeamType } from "@skeldjs/au-core"; +import { Room } from "../../Room"; +import { BaseRole } from "./BaseRole"; + +/** + * Shapeshifter Role + * + * An Impostor role that can temporarily transform into another player's + * appearance. While transformed, the Shapeshifter looks exactly like the + * target player (name, color, cosmetics) to all other players. + * + * RPC flow (handled by SkeldJS PlayerControl): + * 1. SS selects target → client sends CheckShapeshift RPC (55) to host + * 2. Host/server validates: SS role? Not in cooldown? Target valid? + * 3a. Valid → broadcast Shapeshift RPC (46) → all clients see transformation + * 3b. Invalid → broadcast RejectShapeshift RPC (56) + * 4. Duration expires → auto-revert to original appearance + * + * Duration and cooldown come from role settings. + */ +export class ShapeshifterRole extends BaseRole { + roleType = RoleType.Shapeshifter; + teamType = RoleTeamType.Impostor; + + /** Whether the SS is currently transformed. */ + isTransformed: boolean = false; + + /** The player whose appearance was copied. */ + transformTarget: Player | null = null; + + /** Remaining duration of current transformation (seconds). */ + private _transformTimer: number = 0; + + /** Remaining cooldown before next transformation (seconds). */ + private _cooldownTimer: number = 0; + + /** Original outfit data saved before transformation. */ + private _savedOutfit: any = null; + + get duration(): number { + const settings = this.room.settings.roleSettings as any; + return settings.shapeshifterDuration ?? 30; + } + + get cooldown(): number { + const settings = this.room.settings.roleSettings as any; + return settings.shapeshifterCooldown ?? 10; + } + + onGameStart(): void { + this.isActive = true; + this.isTransformed = false; + this.transformTarget = null; + this._transformTimer = 0; + this._cooldownTimer = 0; + this.room.logger.info("%s is the Shapeshifter (duration: %ss, cooldown: %ss)", + this.player, this.duration, this.cooldown); + } + + /** + * Transform into the target player's appearance. + */ + onAbilityUse(target?: Player): boolean { + if (!target) { + this.room.logger.warn("%s (SS) attempted to shapeshift but no target specified", this.player); + return false; + } + + if (!this.canUseAbility()) { + this.room.logger.warn("%s (SS) ability on cooldown (%ss remaining)", + this.player, Math.ceil(this._cooldownTimer)); + return false; + } + + if (this.isTransformed) { + this.room.logger.warn("%s (SS) attempted to shapeshift while already transformed", this.player); + return false; + } + + const targetInfo = target.getPlayerInfo(); + if (targetInfo?.isDead) { + this.room.logger.warn("%s (SS) attempted to shapeshift into dead player %s", this.player, target); + return false; + } + + // Save original outfit and apply target's appearance + const playerInfo = this.player.getPlayerInfo(); + if (playerInfo?.currentOutfit) { + this._savedOutfit = { + name: playerInfo.currentOutfit.name, + color: playerInfo.currentOutfit.color, + hatId: playerInfo.currentOutfit.hatId, + skinId: playerInfo.currentOutfit.skinId, + visorId: playerInfo.currentOutfit.visorId, + petId: playerInfo.currentOutfit.petId, + }; + + const targetOutfit = targetInfo?.currentOutfit; + if (targetOutfit) { + playerInfo.currentOutfit.name = targetOutfit.name; + playerInfo.currentOutfit.color = targetOutfit.color; + playerInfo.currentOutfit.hatId = targetOutfit.hatId; + playerInfo.currentOutfit.skinId = targetOutfit.skinId; + playerInfo.currentOutfit.visorId = targetOutfit.visorId; + playerInfo.currentOutfit.petId = targetOutfit.petId; + } + } + + this.isTransformed = true; + this.transformTarget = target; + this._transformTimer = this.duration; + + this.room.logger.info("%s (SS) transformed into %s for %ss", + this.player, target, this.duration); + + return true; + } + + /** + * Revert to original appearance. + */ + revertTransform(): void { + if (!this.isTransformed) return; + + const playerInfo = this.player.getPlayerInfo(); + if (playerInfo?.currentOutfit && this._savedOutfit) { + playerInfo.currentOutfit.name = this._savedOutfit.name; + playerInfo.currentOutfit.color = this._savedOutfit.color; + playerInfo.currentOutfit.hatId = this._savedOutfit.hatId; + playerInfo.currentOutfit.skinId = this._savedOutfit.skinId; + playerInfo.currentOutfit.visorId = this._savedOutfit.visorId; + playerInfo.currentOutfit.petId = this._savedOutfit.petId; + + // Push data update so clients see the reverted appearance + playerInfo.pushDataState(1 as any); + } + + this.isTransformed = false; + this.transformTarget = null; + this._savedOutfit = null; + + this.room.logger.info("%s (SS) reverted to original appearance", this.player); + + // Start cooldown + this._cooldownTimer = this.cooldown; + } + + onFixedUpdate(): void { + if (this.isTransformed && this._transformTimer > 0) { + this._transformTimer -= 0.1; + if (this._transformTimer <= 0) { + this._transformTimer = 0; + this.revertTransform(); + } + } + + if (!this.isTransformed && this._cooldownTimer > 0) { + this._cooldownTimer -= 0.1; + if (this._cooldownTimer <= 0) { + this._cooldownTimer = 0; + } + } + } + + onDeath(): boolean { + if (this.isTransformed) { + this.revertTransform(); + } + this.isActive = false; + return true; + } + + onGameEnd(): void { + if (this.isTransformed) { + this.revertTransform(); + } + this.isActive = false; + } +} diff --git a/src/game/roles/TrackerRole.ts b/src/game/roles/TrackerRole.ts index 79c8af96e..2a0f6eec2 100644 --- a/src/game/roles/TrackerRole.ts +++ b/src/game/roles/TrackerRole.ts @@ -6,7 +6,7 @@ import { BaseRole } from "./BaseRole"; * Tracker Role * * Can mark a target player and receive periodic position updates - * (arrow indicators pointing to the target's direction). + * (arrow indicators pointing to the target's direction on the map). * * This is a Crewmate role. */ @@ -17,33 +17,37 @@ export class TrackerRole extends BaseRole { /** The currently tracked player, if any. */ trackedTarget: Player | null = null; - /** Timer for periodic position updates. */ - private _trackingTimer: NodeJS.Timeout | null = null; + /** Remaining tracking duration (seconds). */ + private _trackingTimer: number = 0; - /** How long the tracking has been active. */ - private _trackingElapsed: number = 0; + /** Remaining cooldown before next track (seconds). */ + private _cooldownTimer: number = 0; + + /** Time since last position update (seconds). */ + private _updateTimer: number = 0; get cooldown(): number { - return this.room.settings.roleSettings.trackerCooldown || 15; + return (this.room.settings.roleSettings as any).trackerCooldown || 15; } get duration(): number { - return this.room.settings.roleSettings.trackerDuration || 30; + return (this.room.settings.roleSettings as any).trackerDuration || 30; } get delay(): number { - return this.room.settings.roleSettings.trackerDelay || 1; + return (this.room.settings.roleSettings as any).trackerDelay || 1; } onGameStart(): void { this.isActive = true; + this.trackedTarget = null; + this._trackingTimer = 0; + this._cooldownTimer = 0; + this._updateTimer = 0; this.room.logger.info("%s is the Tracker (duration: %ss, cooldown: %ss, delay: %ss)", this.player, this.duration, this.cooldown, this.delay); } - /** - * Use the tracking ability on a target player. - */ onAbilityUse(target?: Player): boolean { if (!target) { this.room.logger.warn("%s (Tracker) attempted to track but no target specified", this.player); @@ -60,9 +64,9 @@ export class TrackerRole extends BaseRole { return false; } - // Start tracking this.trackedTarget = target; - this._trackingElapsed = 0; + this._trackingTimer = this.duration; + this._updateTimer = 0; this.startCooldown(this.cooldown * 1000); this.room.logger.info("%s (Tracker) is now tracking %s", this.player, target); @@ -72,51 +76,67 @@ export class TrackerRole extends BaseRole { { targets: [this.player] } ); - // Start periodic position updates - this._trackingTimer = setInterval(() => { - this.sendPositionUpdate(); - this._trackingElapsed += this.delay; - - // Stop tracking after duration expires - if (this._trackingElapsed >= this.duration) { - this.stopTracking(); - } - }, this.delay * 1000); - return true; } /** * Send a position update about the tracked target to this player. + * The Among Us client displays an arrow pointing to the target's location. */ private sendPositionUpdate(): void { - if (!this.trackedTarget || !this.trackedTarget.characterControl) { + if (!this.trackedTarget?.characterControl) { this.stopTracking(); return; } - // In the actual game, this would send an arrow/indicator RPC - // For now, we log the position update - this.room.logger.debug("%s (Tracker) received position update for %s", - this.player, this.trackedTarget); + // In SaaH mode, the server can directly read the target's position + // and send it to the tracker. The client handles arrow display + // based on the tracked player's transform data. + const transform = this.trackedTarget.characterControl.getComponentSafe( + 2, "CustomNetworkTransform" as any + ) as any; + + if (transform) { + this.room.logger.debug("%s (Tracker) position update for %s: (%.1f, %.1f)", + this.player, this.trackedTarget, + transform.X ?? 0, transform.Y ?? 0); + } } /** * Stop tracking the current target. */ private stopTracking(): void { - if (this._trackingTimer) { - clearInterval(this._trackingTimer); - this._trackingTimer = null; - } - if (this.trackedTarget) { this.room.logger.info("%s (Tracker) stopped tracking %s", this.player, this.trackedTarget); } - this.trackedTarget = null; - this._trackingElapsed = 0; + this._trackingTimer = 0; + this._updateTimer = 0; + } + + onFixedUpdate(): void { + if (this.trackedTarget && this._trackingTimer > 0) { + this._trackingTimer -= 0.1; + this._updateTimer -= 0.1; + + if (this._updateTimer <= 0) { + this.sendPositionUpdate(); + this._updateTimer = this.delay; + } + + if (this._trackingTimer <= 0) { + this.stopTracking(); + } + } + + if (!this.trackedTarget && this._cooldownTimer > 0) { + this._cooldownTimer -= 0.1; + if (this._cooldownTimer <= 0) { + this._cooldownTimer = 0; + } + } } onDeath(): boolean { diff --git a/src/game/roles/ViperRole.ts b/src/game/roles/ViperRole.ts index eaf0ddfd5..aa055fe7d 100644 --- a/src/game/roles/ViperRole.ts +++ b/src/game/roles/ViperRole.ts @@ -17,53 +17,38 @@ export class ViperRole extends BaseRole { teamType = RoleTeamType.Impostor; /** Set of poisoned player IDs awaiting dissolve. */ - private _poisonedPlayers: Set = new Set(); - - /** Timers for each poisoned player. */ - private _poisonTimers: Map = new Map(); + private _poisonedPlayers: Map = new Map(); get dissolveTime(): number { - return this.room.settings.roleSettings.viperDissolveTime || 15; + return (this.room.settings.roleSettings as any).viperDissolveTime || 15; } onGameStart(): void { this.isActive = true; this._poisonedPlayers.clear(); - this._poisonTimers.clear(); - this.room.logger.info("%s is the Viper (dissolve time: %ss)", this.player, this.dissolveTime); } /** * When the Viper kills, the target is poisoned instead of dying immediately. + * Returns false to prevent normal kill processing — Viper handles it. */ onKill(target: Player): boolean { - if (!this.isActive) return true; // Normal kill if ability inactive + if (!this.isActive) return true; this.room.logger.info("%s (Viper) poisoned %s (dissolve in %ss)", this.player, target, this.dissolveTime); - // Mark as poisoned - this._poisonedPlayers.add(target.clientId); + // Track poison with remaining time + this._poisonedPlayers.set(target.clientId, this.dissolveTime); - // Don't kill immediately — the dissolve timer will handle death - // But we still need to prevent the normal kill from processing - // The actual kill happens after the dissolve time - - // Send a subtle message to the target + // Send a message to the target this.room.sendChat( `You feel a sharp sting... something is wrong...`, { targets: [target] } ); - // Set the dissolve timer - const timer = setTimeout(() => { - this.dissolveTarget(target); - }, this.dissolveTime * 1000); - - this._poisonTimers.set(target.clientId, timer); - return false; // Prevent normal kill — Viper handles it } @@ -71,12 +56,9 @@ export class ViperRole extends BaseRole { * Actually kill the poisoned target after the dissolve time. */ private dissolveTarget(target: Player): void { - if (!this._poisonedPlayers.has(target.clientId)) return; - this._poisonedPlayers.delete(target.clientId); - this._poisonTimers.delete(target.clientId); - // Check if the target is already dead + // Check if already dead const playerInfo = target.getPlayerInfo(); if (playerInfo?.isDead) { this.room.logger.debug("%s (Viper) target %s was already dead, skipping dissolve", @@ -86,13 +68,28 @@ export class ViperRole extends BaseRole { this.room.logger.info("%s (Viper) poison dissolved on %s", this.player, target); - // Send death message this.room.sendChat( `${target.username || "Someone"} succumbed to poison!` ); - // The target dies via the normal game engine mechanics - // Poison mechanic is tracked by this role for timing + // Kill via normal game mechanics + target.characterControl?.causeToDie("exiled"); + } + + onFixedUpdate(): void { + for (const [playerId, remaining] of this._poisonedPlayers) { + const newRemaining = remaining - 0.1; + if (newRemaining <= 0) { + const target = this.room.players.get(playerId); + if (target) { + this.dissolveTarget(target); + } else { + this._poisonedPlayers.delete(playerId); + } + } else { + this._poisonedPlayers.set(playerId, newRemaining); + } + } } /** @@ -103,14 +100,13 @@ export class ViperRole extends BaseRole { } /** - * Get all currently poisoned player IDs. + * Clear all active poisons. */ - getPoisonedPlayers(): number[] { - return [...this._poisonedPlayers]; + clearAllPoisons(): void { + this._poisonedPlayers.clear(); } onDeath(): boolean { - // Clear all poison when the Viper dies this.clearAllPoisons(); this.isActive = false; return true; @@ -120,15 +116,4 @@ export class ViperRole extends BaseRole { this.clearAllPoisons(); this.isActive = false; } - - /** - * Clear all active poisons (e.g., game over, Viper dies). - */ - private clearAllPoisons(): void { - for (const [, timer] of this._poisonTimers) { - clearTimeout(timer); - } - this._poisonTimers.clear(); - this._poisonedPlayers.clear(); - } } diff --git a/src/game/roles/index.ts b/src/game/roles/index.ts index bc16b4456..5cf687565 100644 --- a/src/game/roles/index.ts +++ b/src/game/roles/index.ts @@ -1,7 +1,11 @@ export * from "./BaseRole"; export * from "./DetectiveRole"; +export * from "./EngineerRole"; +export * from "./GuardianAngelRole"; export * from "./NoisemakerRole"; export * from "./PhantomRole"; export * from "./RoleManager"; +export * from "./ScientistRole"; +export * from "./ShapeshifterRole"; export * from "./TrackerRole"; export * from "./ViperRole"; diff --git a/src/handlers/CmdHandler.ts b/src/handlers/CmdHandler.ts new file mode 100644 index 000000000..3ee04b573 --- /dev/null +++ b/src/handlers/CmdHandler.ts @@ -0,0 +1,284 @@ +import * as util from "util"; + +import { Player, DisconnectReason } from "@skeldjs/au-core"; + +import { Room, MessageSide } from "../Room"; +import { Connection } from "../Connection"; +import { RoomPrivacy, logMaps } from "../Room"; +import { GameMode, GameState, GameMap } from "@skeldjs/au-core"; + +/** + * Context for a /cmd command execution. + * Provides access to the room, caller, and response methods. + */ +export class CmdContext { + constructor( + public readonly room: Room, + public readonly player: Player, + public readonly message: string, + public readonly args: string[] + ) {} + + /** + * Reply to the caller privately (only they see the response). + */ + async reply(message: string, ...fmt: any) { + await this.room.sendChat(util.format(message, ...fmt), { + side: MessageSide.Left, + targets: [this.player] + }); + } + + /** + * Broadcast a message to all players in the room. + */ + async broadcast(message: string, ...fmt: any) { + await this.room.sendChat(util.format(message, ...fmt), { + side: MessageSide.Left + }); + } +} + +export type CmdCallback = (ctx: CmdContext) => Promise | void; + +interface CmdEntry { + name: string; + description: string; + requiresAuth: boolean; + callback: CmdCallback; +} + +/** + * Handler for Among Us vanilla /cmd chat commands. + * + * In Among Us, messages starting with "/cmd" from non-host players are + * only sent to the host. In Waterway's SaaH mode, the server IS the host, + * so the server receives and processes these commands. + * + * Supported commands: + * /cmd help - Show available commands + * /cmd list - List players in the room + * /cmd kick - Kick a player from the room + * /cmd ban - Ban a player from the room + * /cmd room - Set the room name + * /cmd public - Make the room public + * /cmd private - Make the room private + */ +export class CmdHandler { + private commands: Map = new Map(); + + constructor(public readonly room: Room) { + this.registerBuiltinCommands(); + } + + private registerBuiltinCommands(): void { + this.register("help", "Show available commands", false, async (ctx) => { + const available = [...this.commands.values()] + .filter(c => !c.requiresAuth || ctx.room.getConnection(ctx.player)?.isAuthenticated); + + let out = "Available commands:"; + for (const cmd of available) { + out += `\n /cmd ${cmd.name} — ${cmd.description}`; + } + await ctx.reply(out); + }); + + this.register("list", "List players in the room", false, async (ctx) => { + const lines: string[] = []; + for (const [, player] of ctx.room.players) { + const conn = ctx.room.connections.get(player.clientId); + const host = ctx.room.authorityId === player.clientId ? " [HOST]" : ""; + const auth = conn?.isAuthenticated ? ` (${conn.friendCode})` : ""; + const dead = player.getPlayerInfo()?.isDead ? " [DEAD]" : ""; + lines.push(` ${player.username}${host}${auth}${dead}`); + } + const state = GameState[ctx.room.gameState] || "Unknown"; + const map = logMaps[ctx.room.settings.map] || GameMap[ctx.room.settings.map] || "Unknown"; + await ctx.reply( + `Room ${ctx.room.code} | ${ctx.room.players.size} players | ${state} | ${map}\n${lines.join("\n")}` + ); + }); + + this.register("kick", "Kick a player by name or ID. Usage: /cmd kick ", true, async (ctx) => { + const targetName = ctx.args.join(" "); + if (!targetName) { + await ctx.reply("Usage: /cmd kick "); + return; + } + + const target = this.findPlayer(targetName); + if (!target) { + await ctx.reply("Player not found: %s", targetName); + return; + } + + const targetConn = ctx.room.getConnection(target); + if (!targetConn) { + await ctx.reply("Cannot kick: player has no connection"); + return; + } + + await targetConn.disconnect(DisconnectReason.Kicked); + await ctx.broadcast( + "%s was kicked by %s", + target.username, ctx.player.username + ); + }); + + this.register("ban", "Ban a player by name or ID. Usage: /cmd ban ", true, async (ctx) => { + const targetName = ctx.args.join(" "); + if (!targetName) { + await ctx.reply("Usage: /cmd ban "); + return; + } + + const target = this.findPlayer(targetName); + if (!target) { + await ctx.reply("Player not found: %s", targetName); + return; + } + + ctx.room.banPlayer(target); + await ctx.broadcast( + "%s was banned by %s", + target.username, ctx.player.username + ); + }); + + this.register("room", "Set room name. Usage: /cmd room ", true, async (ctx) => { + const name = ctx.args.join(" "); + if (!name) { + ctx.room.clearRoomNameOverride(); + await ctx.reply("Room name override cleared."); + return; + } + ctx.room.setRoomNameOverride(name); + await ctx.broadcast( + "Room name set to: %s", name + ); + }); + + this.register("public", "Make the room public", true, async (ctx) => { + ctx.room.privacy = RoomPrivacy.Public; + await ctx.broadcast("Room is now PUBLIC"); + }); + + this.register("private", "Make the room private", true, async (ctx) => { + ctx.room.privacy = RoomPrivacy.Private; + await ctx.broadcast("Room is now PRIVATE"); + }); + } + + /** + * Register a custom /cmd command. + */ + register(name: string, description: string, requiresAuth: boolean, callback: CmdCallback): void { + this.commands.set(name.toLowerCase(), { + name: name.toLowerCase(), + description, + requiresAuth, + callback, + }); + } + + /** + * Parse and execute a /cmd message. + * @param message The full message after "/cmd " prefix + */ + async handleMessage(player: Player, message: string): Promise { + const parts = this.splitArgs(message); + const commandName = parts.shift()?.toLowerCase(); + + if (!commandName) { + // Just "/cmd" — show help + const cmd = this.commands.get("help"); + if (cmd) { + const ctx = new CmdContext(this.room, player, message, []); + await cmd.callback(ctx); + } + return true; + } + + const cmd = this.commands.get(commandName); + if (!cmd) { + await this.room.sendChat( + `Unknown command: /cmd ${commandName}. Use /cmd help for available commands.`, + { side: MessageSide.Left, targets: [player] } + ); + return true; + } + + if (cmd.requiresAuth) { + const conn = this.room.getConnection(player); + if (!conn?.isAuthenticated) { + await this.room.sendChat( + "You must be authenticated to use this command. Log in with your Among Us account first.", + { side: MessageSide.Left, targets: [player] } + ); + return true; + } + } + + const ctx = new CmdContext(this.room, player, message, parts); + try { + await cmd.callback(ctx); + } catch (e) { + ctx.room.logger.error("Error executing /cmd %s: %s", commandName, e); + await ctx.reply("Error executing command."); + } + + return true; + } + + /** + * Find a player by name (partial match) or client ID. + */ + private findPlayer(query: string): Player | undefined { + // Try exact client ID match + const id = parseInt(query); + if (!isNaN(id)) { + return this.room.players.get(id); + } + + // Try case-insensitive name match + const lower = query.toLowerCase(); + for (const [, player] of this.room.players) { + if (player.username.toLowerCase().includes(lower)) { + return player; + } + } + + return undefined; + } + + /** + * Split a command string into arguments, respecting quoted strings. + */ + private splitArgs(input: string): string[] { + const args: string[] = []; + let current = ""; + let inQuote = false; + let quoteChar = ""; + + for (let i = 0; i < input.length; i++) { + const ch = input[i]; + if (!inQuote && (ch === '"' || ch === "'")) { + inQuote = true; + quoteChar = ch; + } else if (inQuote && ch === quoteChar) { + inQuote = false; + quoteChar = ""; + } else if (!inQuote && ch === " ") { + if (current) { + args.push(current); + current = ""; + } + } else { + current += ch; + } + } + if (current) args.push(current); + + return args; + } +} diff --git a/src/handlers/index.ts b/src/handlers/index.ts index 2397549fe..4b4d581f1 100644 --- a/src/handlers/index.ts +++ b/src/handlers/index.ts @@ -1,3 +1,4 @@ export * from "./ChatCommandHandler"; +export * from "./CmdHandler"; export * from "./Plugin"; export * from "./PluginLoader"; From 11116db7e0e484c227f356561c6415f4e23dcb0a Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sat, 20 Jun 2026 22:03:07 +0800 Subject: [PATCH 09/17] Fix meeting --- CHANGELOG.txt | 18 ++++++++++++++++++ src/Room.ts | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 000000000..ff00e09a6 --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,18 @@ + +## Phase 6: 额外修复 (2026-06-20) + +### Room.ts — 双重会议防护 +- 新增 meetingInProgress: boolean 字段 +- player.startmeeting 事件: 检测 meetingInProgress 标志,重复触发时抑制并记录警告 +- component.despawn 事件: MeetingHud 销毁时重置 meetingInProgress = false +- _reset(): 游戏结束时重置 meetingInProgress = false +- processFixedUpdate(): 调用 roleManager.handleFixedUpdate() 和 gameModeManager 勾子 + +### Room.ts — 未知RPC容错处理 +- handleRpcMessage(): 当 component.parseRemoteCall 无法解析 RPC 但 tag 已知时 + - 降级为 warn 日志 (非 error) + - 仍按 acceptUnknownGameData 配置转发,不中断游戏流程 + - 防止 SetScanner (Rpc15) 等已知但因构建/版本不匹配而解析失败的 RPC 导致游戏异常 + +### Room.ts — /cmd 广播抑制修复 +- handleRpcMessage(): 修复返回逻辑 — /cmd 消息抑制后不广播给非目标玩家 diff --git a/src/Room.ts b/src/Room.ts index 1b59a737e..f37ffa971 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -341,6 +341,12 @@ export class Room extends StatefulRoom { */ gameModeManager: HideAndSeekManager | null; + /** + * Whether a meeting is currently in progress. Prevents duplicate + * meeting starts from PlayerStartMeetingEvent firing multiple times. + */ + meetingInProgress: boolean; + /** * The role manager for this room, handling role assignment and lifecycle. */ @@ -393,6 +399,7 @@ export class Room extends StatefulRoom { this.roomNameOverride = ""; this.eventTargets = []; this.gameModeManager = null; + this.meetingInProgress = false; this.roleManager = new RoleManager(this); this.cmdHandler = new CmdHandler(this); @@ -504,6 +511,13 @@ export class Room extends StatefulRoom { }); this.on("player.startmeeting", ev => { + if (this.meetingInProgress) { + this.logger.warn("Duplicate meeting start suppressed (body: %s, caller: %s)", + ev.body, ev.player); + return; + } + this.meetingInProgress = true; + if (ev.body === "emergency") { this.logger.info("Meeting started (emergency meeting)"); } else { @@ -558,6 +572,13 @@ export class Room extends StatefulRoom { // Route through role manager for role-specific death behavior (e.g., Phantom) this.roleManager.handleDeath(ev.player); }); + + // Reset meetingInProgress flag when MeetingHud is despawned + this.on("component.despawn", (ev: any) => { + if (ev.component && ev.component.spawnType === SpawnType.MeetingHud) { + this.meetingInProgress = false; + } + }); } protected _reset() { @@ -565,6 +586,7 @@ export class Room extends StatefulRoom { this.gameModeManager.destroy(); this.gameModeManager = null; } + this.meetingInProgress = false; this.roleManager.handleGameEnd(); // Don't clear this.players — lobby connections stay // Clear only game-specific object state (players/connections stay) @@ -871,8 +893,23 @@ export class Room extends StatefulRoom { if (message.child instanceof UnknownRpcMessage) { const parsedRpc = component.parseRemoteCall(message.child.messageTag, message.child.dataReader); if (!parsedRpc) { + // If the component couldn't parse this RPC but we know the tag name, + // log a warning and forward it anyway (don't break game flow). + const tagName = RpcMessageTag[message.child.messageTag]; + if (tagName) { + this.logger.warn( + "Component %s (netId=%s) couldn't parse RPC %s (%s) from %s — forwarding as-is", + SpawnType[component.spawnType] || "Unknown", + component.netId, + tagName, + message.child.messageTag, + senderPlayer + ); + // Forward the raw message to clients so game flow isn't broken + return this.server.config.socket.acceptUnknownGameData; + } this.logger.error("Unknown remote procedure call from player %s for component net id %s, %s: message tag %s", - senderPlayer, component.netId, SpawnType[component.spawnType] || "Unknown", RpcMessageTag[message.child.messageTag] || message.child.messageTag); + senderPlayer, component.netId, SpawnType[component.spawnType] || "Unknown", tagName || message.child.messageTag); return this.server.config.socket.acceptUnknownGameData; } await component.handleRemoteCall(parsedRpc); From 777dbbd0a89d66ea4d023c2e5a3116a96283516a Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sat, 20 Jun 2026 22:25:19 +0800 Subject: [PATCH 10/17] Fix --- CHANGELOG.txt | 23 ++++++++++++++++++++ src/Room.ts | 58 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ff00e09a6..2a16159f5 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -16,3 +16,26 @@ ### Room.ts — /cmd 广播抑制修复 - handleRpcMessage(): 修复返回逻辑 — /cmd 消息抑制后不广播给非目标玩家 + +## Phase 7: 额外修复 (2026-06-20 #2) + +### Room.ts — 游戏结束清理 +- handleEndGame(): 清除没有活跃连接的 stale 玩家 (this.players) +- handleEndGame(): 清除 stale PlayerInfo (this.playerInfo) +- handleEndGame(): 清空 waitingForHost +- 修复 "4个人却显示7个" 的玩家计数错误 + +### Room.ts — Rejoin 循环修复 +- handleRemoteJoin(): 加入 waitingForHost 前移除同 clientId 的旧条目 +- 防止同一玩家断线重连后在 waitingForHost 中累积重复条目 + +## Phase 8: 玩家离开/加入修复 (2026-06-20 #3) + +### Room.ts — 玩家离开时广播 Despawn +- handleRemoteLeave(): 在调用 handleLeave 之前收集离开玩家的 PlayerInfo 和 PlayerControl 的 DespawnMessage +- handleRemoteLeave(): 将 DespawnMessage 与 S2CRemovePlayerMessage 一起广播给所有剩余客户端 +- 修复: 玩家离开后,其角色对象在其他玩家屏幕上残留(幽灵玩家) + +### Room.ts — 加入黑屏修复 +- handleSceneChangeMessage(): player-authority 模式下也发送全部现有对象 spawn (getExistingObjectSpawn) +- 之前只发 server-owned objects,导致加入玩家缺少必要数据卡在黑屏 diff --git a/src/Room.ts b/src/Room.ts index f37ffa971..872c2242e 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -1060,8 +1060,13 @@ export class Room extends StatefulRoom { [player] ); } else { + // In player-authority mode, send ALL existing object spawns + // (not just server-owned). The joining client needs the full + // game state to avoid getting stuck on a black screen. + // The host client will also send its spawn data, but sending + // everything from the server ensures the client doesn't block. await this.broadcastImmediate( - this.getServerOwnedObjectSpawn(), + this.getExistingObjectSpawn(), undefined, [player], ); @@ -1575,6 +1580,14 @@ export class Room extends StatefulRoom { if (this.gameState === GameState.Ended) { if (!this.isAuthoritative && joiningClient.clientId !== this.authorityId) { + // Remove any stale waiting entry for this clientId (from a previous + // disconnected connection) before adding the new one. + for (const waiting of this.waitingForHost) { + if (waiting.clientId === joiningClient.clientId) { + this.waitingForHost.delete(waiting); + break; + } + } this.waitingForHost.add(joiningClient); await joiningClient.sendPacket( @@ -1642,6 +1655,21 @@ export class Room extends StatefulRoom { await this.gameModeManager.handlePlayerDisconnect(leavingPlayer); } + // Collect despawn messages for the leaving player's networked objects + // BEFORE calling handleLeave (which removes them from our internal maps). + // These must be broadcast to all remaining clients so the ghost + // PlayerControl/PlayerInfo don't persist on other players' screens. + const despawnMessages: BaseGameDataMessage[] = []; + if (leavingPlayer) { + const playerInfo = leavingPlayer.getPlayerInfo(); + if (playerInfo) { + despawnMessages.push(new DespawnMessage(playerInfo.netId)); + } + if (leavingPlayer.characterControl) { + despawnMessages.push(new DespawnMessage(leavingPlayer.characterControl.netId)); + } + } + await this.handleLeave(leavingConnection.clientId); if (this.connections.size === 0) { @@ -1673,6 +1701,7 @@ export class Room extends StatefulRoom { } } + // Broadcast S2CRemovePlayerMessage + object despawns to all remaining clients const promises = []; for (const [, otherClient] of this.connections) { promises.push(otherClient.sendPacket( @@ -1684,7 +1713,8 @@ export class Room extends StatefulRoom { leavingConnection.clientId, reason, this.getClientAwareAuthorityId(otherClient), - ) + ), + ...despawnMessages ] ) )); @@ -1755,12 +1785,34 @@ export class Room extends StatefulRoom { async handleEndGame(reason: GameOverReason, intent?: EndGameIntent) { const ev = await this.emit(new RoomGameEndEvent(this, reason, intent)); if (ev.canceled) return; - + await this.flushMessages(); await this.broadcastImmediate([], [new EndGameMessage(this.code.id, reason, false)]); await super.handleEndGame(reason); + // Clean up stale players — players without active connections who + // will get new clientIds on rejoin. Prevents "4 players but shows 7" bug. + const staleClientIds: number[] = []; + for (const [clientId, player] of this.players) { + if (!this.connections.has(clientId)) { + staleClientIds.push(clientId); + } + } + for (const clientId of staleClientIds) { + this.players.delete(clientId); + } + + // Also clean up PlayerInfo for stale players + for (const [playerId, playerInfo] of this.playerInfo) { + if (!this.connections.has(playerInfo.clientId)) { + this.playerInfo.delete(playerId); + } + } + + // Clear waitingForHost — game is over, no host needed until next game + this.waitingForHost.clear(); + this.logger.info("Game ended: %s", GameOverReason[ev.reason]); await this.flushMessages(); await this.updateAllClientAwareAuthority(); From c6f1cf2b520ba1eb762bad36c8a02200ba705428 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sat, 20 Jun 2026 22:59:21 +0800 Subject: [PATCH 11/17] Fix --- src/Room.ts | 70 ++++++++++++++++++++--------------- src/game/roles/RoleManager.ts | 14 ++++--- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/Room.ts b/src/Room.ts index 872c2242e..46a832e48 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -1655,18 +1655,17 @@ export class Room extends StatefulRoom { await this.gameModeManager.handlePlayerDisconnect(leavingPlayer); } - // Collect despawn messages for the leaving player's networked objects - // BEFORE calling handleLeave (which removes them from our internal maps). - // These must be broadcast to all remaining clients so the ghost - // PlayerControl/PlayerInfo don't persist on other players' screens. - const despawnMessages: BaseGameDataMessage[] = []; + // Collect netIds of objects to despawn BEFORE calling handleLeave + // (which removes them from internal maps). These must be broadcast + // to all remaining clients so ghost objects don't persist. + const despawnNetIds: number[] = []; if (leavingPlayer) { const playerInfo = leavingPlayer.getPlayerInfo(); if (playerInfo) { - despawnMessages.push(new DespawnMessage(playerInfo.netId)); + despawnNetIds.push(playerInfo.netId); } if (leavingPlayer.characterControl) { - despawnMessages.push(new DespawnMessage(leavingPlayer.characterControl.netId)); + despawnNetIds.push(leavingPlayer.characterControl.netId); } } @@ -1701,21 +1700,31 @@ export class Room extends StatefulRoom { } } - // Broadcast S2CRemovePlayerMessage + object despawns to all remaining clients + // Broadcast S2CRemovePlayerMessage to all remaining clients const promises = []; for (const [, otherClient] of this.connections) { + const rootMessages: BaseRootMessage[] = [ + new S2CRemovePlayerMessage( + this.code.id, + leavingConnection.clientId, + reason, + this.getClientAwareAuthorityId(otherClient), + ) + ]; + + // Wrap despawn messages in a GameDataMessage so they reach the + // client's game data layer (DespawnMessage is GameData, not Root). + if (despawnNetIds.length > 0) { + rootMessages.push(new GameDataMessage( + this.code.id, + despawnNetIds.map(netId => new DespawnMessage(netId)) + )); + } + promises.push(otherClient.sendPacket( new ReliablePacket( otherClient.getNextNonce(), - [ - new S2CRemovePlayerMessage( - this.code.id, - leavingConnection.clientId, - reason, - this.getClientAwareAuthorityId(otherClient), - ), - ...despawnMessages - ] + rootMessages ) )); } @@ -1769,7 +1778,7 @@ export class Room extends StatefulRoom { this.logger.info("Game started"); // Assign roles to players - this.roleManager.assignRoles(); + await this.roleManager.assignRoles(); // Start mode-specific logic after game is fully initialized if (this.gameModeManager instanceof HideAndSeekManager) { @@ -1791,10 +1800,21 @@ export class Room extends StatefulRoom { await this.broadcastImmediate([], [new EndGameMessage(this.code.id, reason, false)]); await super.handleEndGame(reason); - // Clean up stale players — players without active connections who - // will get new clientIds on rejoin. Prevents "4 players but shows 7" bug. + // Fully reset player state on game end. + // The previous game's Player/PlayerInfo entries are no longer valid. + // Players who stay in the lobby will be re-created with fresh state + // when the next game starts or when they rejoin. + const stalePlayerIds: number[] = []; + for (const [playerId] of this.playerInfo) { + stalePlayerIds.push(playerId); + } + for (const playerId of stalePlayerIds) { + this.playerInfo.delete(playerId); + } + // Keep only players with active connections (they'll rejoin next game). + // Remove stale entries whose connections have been closed. const staleClientIds: number[] = []; - for (const [clientId, player] of this.players) { + for (const [clientId] of this.players) { if (!this.connections.has(clientId)) { staleClientIds.push(clientId); } @@ -1803,14 +1823,6 @@ export class Room extends StatefulRoom { this.players.delete(clientId); } - // Also clean up PlayerInfo for stale players - for (const [playerId, playerInfo] of this.playerInfo) { - if (!this.connections.has(playerInfo.clientId)) { - this.playerInfo.delete(playerId); - } - } - - // Clear waitingForHost — game is over, no host needed until next game this.waitingForHost.clear(); this.logger.info("Game ended: %s", GameOverReason[ev.reason]); diff --git a/src/game/roles/RoleManager.ts b/src/game/roles/RoleManager.ts index 5b5697e50..2ca9defdc 100644 --- a/src/game/roles/RoleManager.ts +++ b/src/game/roles/RoleManager.ts @@ -43,7 +43,7 @@ export class RoleManager { * Assign roles to players based on the room's role settings. * Called when the game starts. */ - assignRoles(): void { + async assignRoles(): Promise { const settings = this.room.settings; const roleChances = settings.roleSettings.roleChances; @@ -101,7 +101,7 @@ export class RoleManager { // Roll the dice const roll = Math.random() * 100; if (roll <= roleChance.chance) { - this.assignRoleToPlayer(player, RoleCtor, roleType); + await this.assignRoleToPlayer(player, RoleCtor, roleType); assignedCounts[roleType]!++; } } @@ -121,18 +121,22 @@ export class RoleManager { /** * Create and assign a role to a specific player. */ - assignRoleToPlayer( + async assignRoleToPlayer( player: Player, RoleCtor: new (room: Room, player: Player) => BaseRole, roleType: RoleType - ): BaseRole { + ): Promise { const role = new RoleCtor(this.room, player); this.activeRoles.set(player.clientId, role); // The player's role is tracked by this manager // The client-facing role is set via PlayerControl.setRole RPC + // Must await — setRole is async and broadcasts the SetRole RPC to clients if (player.characterControl) { - (player.characterControl as any).setRole(roleType); + const roleClass = this.room.registeredRoles.get(roleType); + if (roleClass) { + await player.characterControl.setRole(roleClass); + } } // Initialize the role From c5432f95addfd28a547a1d160e14d3f057136b63 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sat, 20 Jun 2026 23:23:34 +0800 Subject: [PATCH 12/17] Fix --- src/Room.ts | 25 ------------------------- src/WaterwayServer.ts | 2 +- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/src/Room.ts b/src/Room.ts index 46a832e48..e61eb0daa 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -1800,31 +1800,6 @@ export class Room extends StatefulRoom { await this.broadcastImmediate([], [new EndGameMessage(this.code.id, reason, false)]); await super.handleEndGame(reason); - // Fully reset player state on game end. - // The previous game's Player/PlayerInfo entries are no longer valid. - // Players who stay in the lobby will be re-created with fresh state - // when the next game starts or when they rejoin. - const stalePlayerIds: number[] = []; - for (const [playerId] of this.playerInfo) { - stalePlayerIds.push(playerId); - } - for (const playerId of stalePlayerIds) { - this.playerInfo.delete(playerId); - } - // Keep only players with active connections (they'll rejoin next game). - // Remove stale entries whose connections have been closed. - const staleClientIds: number[] = []; - for (const [clientId] of this.players) { - if (!this.connections.has(clientId)) { - staleClientIds.push(clientId); - } - } - for (const clientId of staleClientIds) { - this.players.delete(clientId); - } - - this.waitingForHost.clear(); - this.logger.info("Game ended: %s", GameOverReason[ev.reason]); await this.flushMessages(); await this.updateAllClientAwareAuthority(); diff --git a/src/WaterwayServer.ts b/src/WaterwayServer.ts index f32e94c6c..38d47123b 100644 --- a/src/WaterwayServer.ts +++ b/src/WaterwayServer.ts @@ -2100,7 +2100,7 @@ export class WaterwayServer extends EventEmitter { return connection.disconnect(DisconnectReason.Banned); } - if (ev.alteredRoom.connections.size >= ev.alteredRoom.settings.maxPlayers) { + if (ev.alteredRoom.players.size >= ev.alteredRoom.settings.maxPlayers) { this.logger.warn("%s attempted to join %s but it was full", connection, foundRoom); return connection.disconnect(DisconnectReason.GameFull); From adc19662127ea76cc4bc1f5c44abd93cb5893082 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sat, 20 Jun 2026 23:43:07 +0800 Subject: [PATCH 13/17] Fix --- src/Room.ts | 15 +++++++++++++-- src/WaterwayServer.ts | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Room.ts b/src/Room.ts index e61eb0daa..9dd9858ab 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -588,9 +588,16 @@ export class Room extends StatefulRoom { } this.meetingInProgress = false; this.roleManager.handleGameEnd(); - // Don't clear this.players — lobby connections stay - // Clear only game-specific object state (players/connections stay) + + // Despawn all objects from the previous game (PlayerInfo, PlayerControl, + // ShipStatus, MeetingHud, etc.). This is the safe point to clean up since + // the client has already processed the EndGameMessage and built its winner + // list from the live PlayerInfo data. + for (const [, component] of this.networkedObjects) { + this.despawnComponent(component); + } this.networkedObjects.clear(); + this.playerInfo.clear(); this.objectList.length = 0; this.messageStream = []; this.settings = new GameSettings; @@ -1792,6 +1799,10 @@ export class Room extends StatefulRoom { } async handleEndGame(reason: GameOverReason, intent?: EndGameIntent) { + // Prevent double-processing: flushEndGameIntents (server-side) and + // the host client's EndGameMessage may both trigger handleEndGame. + if (this.gameState === GameState.Ended) return; + const ev = await this.emit(new RoomGameEndEvent(this, reason, intent)); if (ev.canceled) return; diff --git a/src/WaterwayServer.ts b/src/WaterwayServer.ts index 38d47123b..e0611f167 100644 --- a/src/WaterwayServer.ts +++ b/src/WaterwayServer.ts @@ -1708,6 +1708,19 @@ export class WaterwayServer extends EventEmitter { await targetConnection.disconnect(message.banned ? DisconnectReason.Banned : DisconnectReason.Kicked); } + async handleRemovePlayerMessage(message: C2SRemovePlayerMessage, sender: Connection) { + // C2SRemovePlayer is sent by the host client to remove a player from the + // lobby. Disconnect the target player's connection. + if (!sender.room) return; + + const targetConn = sender.room.connections.get(message.clientId); + if (!targetConn) return; + + this.logger.info("%s removed %s from the lobby", + sender, targetConn); + await targetConn.disconnect(message.reason); + } + async handleGetGameListMessage(message: C2SGetGameListMessage, sender: Connection) { const listingIp = sender.remoteInfo.address === "127.0.0.1" ? "127.0.0.1" @@ -1852,6 +1865,7 @@ export class WaterwayServer extends EventEmitter { if (message instanceof EndGameMessage) return await this.handleEndGameMessage(message, sender); if (message instanceof KickPlayerMessage) return await this.handleKickPlayerMessage(message, sender); if (message instanceof C2SGetGameListMessage) return await this.handleGetGameListMessage(message, sender); + if (message instanceof C2SRemovePlayerMessage) return await this.handleRemovePlayerMessage(message, sender); if (message instanceof SetActivePodTypeMessage) { // not sure what to do with this.. From 350d6d7fca707fc4e068646c29141a895e39f347 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sun, 21 Jun 2026 00:01:27 +0800 Subject: [PATCH 14/17] Fix --- src/Room.ts | 39 ++++++++++----------------------------- src/WaterwayServer.ts | 8 +++++++- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/Room.ts b/src/Room.ts index 9dd9858ab..60c6f3a13 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -1587,31 +1587,13 @@ export class Room extends StatefulRoom { if (this.gameState === GameState.Ended) { if (!this.isAuthoritative && joiningClient.clientId !== this.authorityId) { - // Remove any stale waiting entry for this clientId (from a previous - // disconnected connection) before adding the new one. - for (const waiting of this.waitingForHost) { - if (waiting.clientId === joiningClient.clientId) { - this.waitingForHost.delete(waiting); - break; - } - } - this.waitingForHost.add(joiningClient); - - await joiningClient.sendPacket( - new ReliablePacket( - joiningClient.getNextNonce(), - [ - new WaitForHostMessage( - this.code.id, - joiningClient.clientId - ) - ] - ) - ); - - this.logger.info("%s joined, waiting for host", - joiningPlayer); - return; + // Game is over — the previous host may be gone. + // Auto-promote this player as the new authority instead of + // making them wait indefinitely. + this.logger.info("%s joined after game end, becoming new authority", joiningClient); + this.authorityId = joiningClient.clientId; + await joiningPlayer.emit(new PlayerSetAuthoritativeEvent(this, joiningPlayer)); + // Fall through to normal join flow below } } @@ -1748,10 +1730,9 @@ export class Room extends StatefulRoom { } async handleStartGame(startedBy?: Player) { - // Reset room state if restarting after a previous game ended - if (this.gameState === GameState.Ended) { - this._reset(); - } + // Always reset — despawns objects from any previous game regardless + // of whether it ended cleanly or was abandoned mid-game. + this._reset(); this.gameState = GameState.Started; diff --git a/src/WaterwayServer.ts b/src/WaterwayServer.ts index e0611f167..a64555f78 100644 --- a/src/WaterwayServer.ts +++ b/src/WaterwayServer.ts @@ -2114,7 +2114,13 @@ export class WaterwayServer extends EventEmitter { return connection.disconnect(DisconnectReason.Banned); } - if (ev.alteredRoom.players.size >= ev.alteredRoom.settings.maxPlayers) { + // Only block if room is actually full with DIFFERENT players. + // A player reconnecting after game end should not be blocked by + // their own previous Player entry still being in the room. + const roomPlayerCount = ev.alteredRoom.players.size; + const alreadyInRoom = ev.alteredRoom.players.has(connection.clientId); + const effectiveCount = alreadyInRoom ? roomPlayerCount - 1 : roomPlayerCount; + if (effectiveCount >= ev.alteredRoom.settings.maxPlayers) { this.logger.warn("%s attempted to join %s but it was full", connection, foundRoom); return connection.disconnect(DisconnectReason.GameFull); From 4afbc7ba91b9f0d9649b2d7bf2237f63534a662b Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sun, 21 Jun 2026 00:21:01 +0800 Subject: [PATCH 15/17] Fix --- src/Room.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Room.ts b/src/Room.ts index 60c6f3a13..5fbc6f4e8 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -589,14 +589,20 @@ export class Room extends StatefulRoom { this.meetingInProgress = false; this.roleManager.handleGameEnd(); - // Despawn all objects from the previous game (PlayerInfo, PlayerControl, - // ShipStatus, MeetingHud, etc.). This is the safe point to clean up since - // the client has already processed the EndGameMessage and built its winner - // list from the live PlayerInfo data. + // Only despawn server-owned objects (PlayerInfo, netId >= 100000). + // Client/host-owned objects (PlayerControl, ShipStatus, etc.) have + // netIds < 100000 and are managed by the host client in player-authority + // mode. Despawning them causes "non-existent component" errors when + // the host continues sending RPCs to now-destroyed netIds. + const despawning: NetworkedObject[] = []; for (const [, component] of this.networkedObjects) { + if (component.netId >= 100000) { + despawning.push(component); + } + } + for (const component of despawning) { this.despawnComponent(component); } - this.networkedObjects.clear(); this.playerInfo.clear(); this.objectList.length = 0; this.messageStream = []; From 12de84dd4ba81f398e2dc9a12520c9fdb7c944ca Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sun, 21 Jun 2026 00:36:46 +0800 Subject: [PATCH 16/17] Fix --- src/Room.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Room.ts b/src/Room.ts index 5fbc6f4e8..90e01dbb9 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -606,9 +606,8 @@ export class Room extends StatefulRoom { this.playerInfo.clear(); this.objectList.length = 0; this.messageStream = []; - this.settings = new GameSettings; + // Don't reset settings — preserve host-configured map/mode across rounds this.startGameCounter = -1; - this.privacy = RoomPrivacy.Private; this.lastNetId = 100000; this.ownershipGuards.clear(); } @@ -1007,6 +1006,11 @@ export class Room extends StatefulRoom { message.flags, message.components.map(x => x.netId), ); + // Duplicate NetID — the object already exists from a previous spawn. + // Silently skip (host may re-send spawns when syncing lobby state). + if (!object) { + return true; + } for (let i = 0; i < message.components.length; i++) { const data = message.components[i].data; const component = object.components[i]; @@ -1736,9 +1740,11 @@ export class Room extends StatefulRoom { } async handleStartGame(startedBy?: Player) { - // Always reset — despawns objects from any previous game regardless - // of whether it ended cleanly or was abandoned mid-game. - this._reset(); + // Only reset if restarting after a previous game. + // Don't reset on first start — lobby PlayerInfo objects are needed. + if (this.gameState === GameState.Ended) { + this._reset(); + } this.gameState = GameState.Started; From a532a2caabcfa175c7eeaf9b2f0f698f4eb07ce4 Mon Sep 17 00:00:00 2001 From: Fang-Kaui <145177672+FangkuaiYa@users.noreply.github.com> Date: Sun, 21 Jun 2026 00:40:30 +0800 Subject: [PATCH 17/17] Delete CHANGELOG.txt --- CHANGELOG.txt | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 CHANGELOG.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt deleted file mode 100644 index 2a16159f5..000000000 --- a/CHANGELOG.txt +++ /dev/null @@ -1,41 +0,0 @@ - -## Phase 6: 额外修复 (2026-06-20) - -### Room.ts — 双重会议防护 -- 新增 meetingInProgress: boolean 字段 -- player.startmeeting 事件: 检测 meetingInProgress 标志,重复触发时抑制并记录警告 -- component.despawn 事件: MeetingHud 销毁时重置 meetingInProgress = false -- _reset(): 游戏结束时重置 meetingInProgress = false -- processFixedUpdate(): 调用 roleManager.handleFixedUpdate() 和 gameModeManager 勾子 - -### Room.ts — 未知RPC容错处理 -- handleRpcMessage(): 当 component.parseRemoteCall 无法解析 RPC 但 tag 已知时 - - 降级为 warn 日志 (非 error) - - 仍按 acceptUnknownGameData 配置转发,不中断游戏流程 - - 防止 SetScanner (Rpc15) 等已知但因构建/版本不匹配而解析失败的 RPC 导致游戏异常 - -### Room.ts — /cmd 广播抑制修复 -- handleRpcMessage(): 修复返回逻辑 — /cmd 消息抑制后不广播给非目标玩家 - -## Phase 7: 额外修复 (2026-06-20 #2) - -### Room.ts — 游戏结束清理 -- handleEndGame(): 清除没有活跃连接的 stale 玩家 (this.players) -- handleEndGame(): 清除 stale PlayerInfo (this.playerInfo) -- handleEndGame(): 清空 waitingForHost -- 修复 "4个人却显示7个" 的玩家计数错误 - -### Room.ts — Rejoin 循环修复 -- handleRemoteJoin(): 加入 waitingForHost 前移除同 clientId 的旧条目 -- 防止同一玩家断线重连后在 waitingForHost 中累积重复条目 - -## Phase 8: 玩家离开/加入修复 (2026-06-20 #3) - -### Room.ts — 玩家离开时广播 Despawn -- handleRemoteLeave(): 在调用 handleLeave 之前收集离开玩家的 PlayerInfo 和 PlayerControl 的 DespawnMessage -- handleRemoteLeave(): 将 DespawnMessage 与 S2CRemovePlayerMessage 一起广播给所有剩余客户端 -- 修复: 玩家离开后,其角色对象在其他玩家屏幕上残留(幽灵玩家) - -### Room.ts — 加入黑屏修复 -- handleSceneChangeMessage(): player-authority 模式下也发送全部现有对象 spawn (getExistingObjectSpawn) -- 之前只发 server-owned objects,导致加入玩家缺少必要数据卡在黑屏