diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 733267f..30e6743 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: with: node-version: 20 cache: "pnpm" - - run: make init + - run: make install - run: make build - run: make check-format - run: make test diff --git a/Makefile b/Makefile index a9b3401..fdff863 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -.PHONY: init -init: +.PHONY: install +install: pnpm install .PHONY: build diff --git a/game-template/game/src/index.test-d.ts b/game-template/game/src/index.test-d.ts deleted file mode 100644 index 1695c2c..0000000 --- a/game-template/game/src/index.test-d.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { expectTypeOf, test } from "vitest"; - -import { MatchTester, PlayerMove } from "@lefun/game"; - -import { game, RollGame, RollGameState as GS } from "."; - -test("inside PlayerMove", () => { - expectTypeOf(game).toEqualTypeOf(); - - const move: PlayerMove = { - executeNow() { - // - }, - }; - - expectTypeOf(move.executeNow).parameter(0).toMatchTypeOf<{ - board: GS["B"]; - playerboard: GS["PB"]; - payload: { x: number }; - }>(); - - expectTypeOf(move.executeNow).parameter(0).toMatchTypeOf<{ - board: { count: number }; - }>(); -}); - -test("match tester", () => { - const match = new MatchTester({ game, numPlayers: 2 }); - expectTypeOf(match.board.count).toEqualTypeOf(); -}); diff --git a/game-template/game/src/index.test.ts b/game-template/game/src/index.test.ts deleted file mode 100644 index 2eb7c1b..0000000 --- a/game-template/game/src/index.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { expect, test } from "vitest"; - -import { MatchTester as _MatchTester } from "@lefun/game"; - -import { game, RollGame as G, RollGameState as GS } from "."; - -class MatchTester extends _MatchTester {} - -test("sanity check", () => { - const match = new MatchTester({ game, numPlayers: 2 }); - const { players } = match.board; - - const userId = Object.keys(players)[0]; - - match.makeMove(userId, "roll"); - match.makeMove(userId, "roll", {}, { canFail: true }); - match.makeMove(userId, "moveWithArg", { someArg: "123" }); - match.makeMove(userId, "moveWithArg", { someArg: "123" }, { canFail: true }); - - // Time has no passed yet - expect(match.board.lastSomeBoardMoveValue).toBeUndefined(); - - // Not enough time - match.fastForward(50); - expect(match.board.lastSomeBoardMoveValue).toBeUndefined(); - - // Enough time - match.fastForward(50); - expect(match.board.lastSomeBoardMoveValue).toEqual(3); -}); diff --git a/game-template/game/src/index.ts b/game-template/game/src/index.ts deleted file mode 100644 index 2d2d98c..0000000 --- a/game-template/game/src/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { UserId } from "@lefun/core"; -import { - AutoMove, - BoardMove, - Game, - GameState, - INIT_MOVE, - PlayerMove, -} from "@lefun/game"; - -type Player = { - isRolling: boolean; - diceValue?: number; -}; - -export type Board = { - count: number; - players: Record; - lastSomeBoardMoveValue?: number; -}; - -export type RollGameState = GameState; - -type BMT = { - someBoardMove: never; - someBoardMoveWithArgs: { someArg: number }; -}; - -const moveWithArg: PlayerMove = { - execute() { - // - }, -}; - -const roll: PlayerMove = { - executeNow({ board, userId }) { - board.players[userId].isRolling = true; - }, - execute({ board, userId, random, delayMove }) { - board.players[userId].diceValue = random.d6(); - board.players[userId].isRolling = false; - delayMove("someBoardMove", 100); - delayMove("someBoardMoveWithArgs", { someArg: 3 }, 100); - }, -}; - -const initMove: BoardMove = { - execute() { - // - }, -}; - -const someBoardMove: BoardMove = { - execute() { - // - }, -}; - -const someBoardMoveWithArgs: BoardMove< - RollGameState, - BMT["someBoardMoveWithArgs"], - BMT -> = { - execute({ board, payload }) { - board.lastSomeBoardMoveValue = payload.someArg; - }, -}; - -export const game = { - initialBoards: ({ players }) => ({ - board: { - count: 0, - players: Object.fromEntries( - players.map((userId) => [userId, { isRolling: false }]), - ), - }, - }), - playerMoves: { roll, moveWithArg }, - boardMoves: { [INIT_MOVE]: initMove, someBoardMove, someBoardMoveWithArgs }, - minPlayers: 1, - maxPlayers: 10, -} satisfies Game; - -export const autoMove: AutoMove = ({ random }) => { - if (random.d2() === 1) { - return ["moveWithArg", { someArg: "123" }]; - } - return "roll"; -}; - -export type RollGame = typeof game; diff --git a/game-template/.gitignore b/games/game1-v2.3.0/.gitignore similarity index 100% rename from game-template/.gitignore rename to games/game1-v2.3.0/.gitignore diff --git a/game-template/README.md b/games/game1-v2.3.0/README.md similarity index 100% rename from game-template/README.md rename to games/game1-v2.3.0/README.md diff --git a/game-template/game/.eslintrc.json b/games/game1-v2.3.0/game/.eslintrc.json similarity index 100% rename from game-template/game/.eslintrc.json rename to games/game1-v2.3.0/game/.eslintrc.json diff --git a/game-template/game/package.json b/games/game1-v2.3.0/game/package.json similarity index 87% rename from game-template/game/package.json rename to games/game1-v2.3.0/game/package.json index d3b80ca..e2a8576 100644 --- a/game-template/game/package.json +++ b/games/game1-v2.3.0/game/package.json @@ -1,5 +1,5 @@ { - "name": "roll-game", + "name": "game1-v2.3.0-game", "version": "1.0.0", "description": "Game logic for the minimal example game 'Roll'", "author": "Simon Lemieux", @@ -18,11 +18,11 @@ "devDependencies": { "@lefun/core": "workspace:*", "@lefun/game": "workspace:*", - "rollup": "^4.18.1", + "rollup": "^4.20.0", "rollup-plugin-typescript2": "^0.36.0", "tslib": "^2.6.3", - "typescript": "^5.5.3", - "vitest": "^1.6.0" + "typescript": "^5.5.4", + "vitest": "^2.0.5" }, "peerDependencies": { "@lefun/core": "workspace:*", diff --git a/game-template/game/rollup.config.js b/games/game1-v2.3.0/game/rollup.config.js similarity index 100% rename from game-template/game/rollup.config.js rename to games/game1-v2.3.0/game/rollup.config.js diff --git a/games/game1-v2.3.0/game/src/index.test.ts b/games/game1-v2.3.0/game/src/index.test.ts new file mode 100644 index 0000000..dc4c8fd --- /dev/null +++ b/games/game1-v2.3.0/game/src/index.test.ts @@ -0,0 +1,62 @@ +import { expect, test } from "vitest"; + +import { MatchTester as _MatchTester, MatchTesterOptions } from "@lefun/game"; + +import { autoMove, game, RollGame as G, RollGameState as GS } from "."; + +class MatchTester extends _MatchTester { + constructor(options: Omit, "game" | "autoMove">) { + super({ + ...options, + game, + autoMove, + }); + } +} + +test("sanity check", () => { + const match = new MatchTester({ numPlayers: 2 }); + const { players } = match.board; + + const userId = Object.keys(players)[0]; + + match.makeMove(userId, "roll"); + match.makeMove(userId, "roll", {}, { canFail: true }); + match.makeMove(userId, "moveWithArg", { someArg: "123" }); + match.makeMove(userId, "moveWithArg", { someArg: "123" }, { canFail: true }); + + // Time has no passed yet + expect(match.board.lastSomeBoardMoveValue).toBeUndefined(); + + // Not enough time + match.fastForward(50); + expect(match.board.lastSomeBoardMoveValue).toBeUndefined(); + + // Enough time + match.fastForward(50); + expect(match.board.lastSomeBoardMoveValue).toEqual(3); +}); + +test("turns in tests", () => { + const match = new MatchTester({ numPlayers: 2 }); + + const [p0, p1] = match.board.playerOrder; + + expect(match.meta.players.byId[p0].itsYourTurn).toBe(true); + expect(match.meta.players.byId[p1].itsYourTurn).toBe(false); + + match.makeMove(p0, "roll"); + expect(match.meta.players.byId[p0].itsYourTurn).toBe(false); + expect(match.meta.players.byId[p1].itsYourTurn).toBe(true); + + match.makeMove(p0, "moveWithArg", { someArg: "123" }); + expect(match.meta.players.byId[p0].itsYourTurn).toBe(false); + expect(match.meta.players.byId[p1].itsYourTurn).toBe(true); +}); + +test("bots and turns", async () => { + const match = new MatchTester({ numPlayers: 0, numBots: 2 }); + await match.start(); + expect(match.board.sum).toBeGreaterThanOrEqual(20); + expect(match.matchHasEnded).toBe(true); +}); diff --git a/games/game1-v2.3.0/game/src/index.ts b/games/game1-v2.3.0/game/src/index.ts new file mode 100644 index 0000000..5496997 --- /dev/null +++ b/games/game1-v2.3.0/game/src/index.ts @@ -0,0 +1,153 @@ +import { UserId } from "@lefun/core"; +import { + AutoMove, + BoardMove, + Game, + GameState, + GameStats, + INIT_MOVE, + PlayerMove, +} from "@lefun/game"; + +type Player = { + isRolling: boolean; + diceValue?: number; +}; + +export type Board = { + players: Record; + playerOrder: UserId[]; + currentPlayerIndex: number; + + sum: number; + lastSomeBoardMoveValue?: number; +}; + +export type RollGameState = GameState; + +type MoveWithArgPayload = { someArg: string }; +type BoardMoveWithArgPayload = { someArg: number }; + +export type PMT = { + roll: null; + moveWithArg: MoveWithArgPayload; +}; + +export type BMT = { + someBoardMove: null; + someBoardMoveWithArgs: BoardMoveWithArgPayload; +}; + +const matchStats = [ + { key: "patate", type: "integer" }, +] as const satisfies GameStats; + +const playerStats = [ + { key: "poil", type: "rank", determinesRank: true }, +] as const satisfies GameStats; + +type PM = PlayerMove< + RollGameState, + Payload, + PMT, + BMT, + typeof playerStats, + typeof matchStats +>; + +const moveWithArg: PM = {}; + +const roll: PM = { + executeNow({ board, userId }) { + board.players[userId].isRolling = true; + }, + execute({ + board, + userId, + random, + delayMove, + turns, + logMatchStat, + logPlayerStat, + endMatch, + }) { + const diceValue = random.d6(); + board.players[userId].diceValue = diceValue; + board.players[userId].isRolling = false; + board.sum += diceValue; + + delayMove("someBoardMove", 100); + delayMove("someBoardMoveWithArgs", { someArg: 3 }, 100); + + // Test those types here + logPlayerStat(userId, "poil", 1); + logMatchStat("patate", 1); + + // If it was the player's turn, we go to the next player. + if (userId === board.playerOrder[board.currentPlayerIndex]) { + turns.end(userId); + board.currentPlayerIndex = + (board.currentPlayerIndex + 1) % board.playerOrder.length; + const nextPlayer = board.playerOrder[board.currentPlayerIndex]; + + turns.begin(nextPlayer, { + expiresIn: 60000, + playerMoveOnExpire: ["moveWithArg", { someArg: "0" }], + }); + } + + if (board.sum >= 20) { + endMatch(); + } + }, +}; + +type BM

= BoardMove; + +const initMove: BM = { + execute({ board, turns }) { + turns.begin(board.playerOrder[0]); + }, +}; + +const someBoardMove: BM = { + execute() { + // + }, +}; + +const someBoardMoveWithArgs: BM = { + execute({ board, payload }) { + board.lastSomeBoardMoveValue = payload.someArg; + }, +}; + +export const game = { + initialBoards({ players }) { + return { + board: { + sum: 0, + players: Object.fromEntries( + players.map((userId) => [userId, { isRolling: false }]), + ), + playerOrder: [...players], + currentPlayerIndex: 0, + }, + }; + }, + playerMoves: { roll, moveWithArg }, + boardMoves: { [INIT_MOVE]: initMove, someBoardMove, someBoardMoveWithArgs }, + minPlayers: 1, + maxPlayers: 10, + matchStats, + playerStats, +} satisfies Game; + +export type RollGame = typeof game; + +export const autoMove: AutoMove = ({ random }) => { + if (random.d2() === 1) { + return ["moveWithArg", { someArg: "123" }]; + } + return "roll"; +}; diff --git a/game-template/game/tsconfig.json b/games/game1-v2.3.0/game/tsconfig.json similarity index 100% rename from game-template/game/tsconfig.json rename to games/game1-v2.3.0/game/tsconfig.json diff --git a/game-template/game/vitest.config.ts b/games/game1-v2.3.0/game/vitest.config.ts similarity index 100% rename from game-template/game/vitest.config.ts rename to games/game1-v2.3.0/game/vitest.config.ts diff --git a/game-template/manifest.json b/games/game1-v2.3.0/manifest.json similarity index 100% rename from game-template/manifest.json rename to games/game1-v2.3.0/manifest.json diff --git a/game-template/ui/.babelrc b/games/game1-v2.3.0/ui/.babelrc similarity index 100% rename from game-template/ui/.babelrc rename to games/game1-v2.3.0/ui/.babelrc diff --git a/game-template/ui/.eslintrc.json b/games/game1-v2.3.0/ui/.eslintrc.json similarity index 96% rename from game-template/ui/.eslintrc.json rename to games/game1-v2.3.0/ui/.eslintrc.json index a8594e3..fb3fc98 100644 --- a/game-template/ui/.eslintrc.json +++ b/games/game1-v2.3.0/ui/.eslintrc.json @@ -18,7 +18,7 @@ ["^node:"], ["^@?\\w"], ["^@lefun/"], - ["roll-game"], + ["game1-v2.3.0-game"], ["^"], ["^\\."] ] diff --git a/game-template/ui/index.html b/games/game1-v2.3.0/ui/index.html similarity index 100% rename from game-template/ui/index.html rename to games/game1-v2.3.0/ui/index.html diff --git a/game-template/ui/lingui.config.ts b/games/game1-v2.3.0/ui/lingui.config.ts similarity index 91% rename from game-template/ui/lingui.config.ts rename to games/game1-v2.3.0/ui/lingui.config.ts index 50bc0a0..93b212d 100644 --- a/game-template/ui/lingui.config.ts +++ b/games/game1-v2.3.0/ui/lingui.config.ts @@ -2,7 +2,7 @@ import type { LinguiConfig } from "@lingui/conf"; import { lefunExtractor } from "@lefun/ui/lefunExtractor"; -import { game } from "roll-game"; +import { game } from "game1-v2.3.0-game"; const config: LinguiConfig = { locales: ["en", "fr"], diff --git a/game-template/ui/package.json b/games/game1-v2.3.0/ui/package.json similarity index 91% rename from game-template/ui/package.json rename to games/game1-v2.3.0/ui/package.json index 488c988..d9b76aa 100644 --- a/game-template/ui/package.json +++ b/games/game1-v2.3.0/ui/package.json @@ -1,5 +1,5 @@ { - "name": "roll-ui", + "name": "game1-v2.3.0-ui", "version": "1.0.0", "description": "UI for the minimal example game 'Roll'", "author": "Simon Lemieux", @@ -10,6 +10,7 @@ "types": "dist/types/index.d.ts", "scripts": { "build": "rm -rf ./dist && pnpm rollup --config", + "watch": "pnpm rollup --config --watch", "dev": "pnpm vite --host", "lingui:compile": "pnpm lingui compile", "lingui:extract": "pnpm lingui extract", @@ -38,12 +39,12 @@ "rollup-plugin-copy": "^3.5.0", "rollup-plugin-postcss": "^4.0.2", "rollup-plugin-typescript2": "^0.36.0", - "typescript": "^5.5.3", + "typescript": "^5.5.4", "vite": "^5.3.4" }, "dependencies": { "classnames": "^2.5.1", - "roll-game": "workspace:*" + "game1-v2.3.0-game": "workspace:*" }, "peerDependencies": { "@lefun/core": "workspace:*", diff --git a/game-template/ui/rollup.config.js b/games/game1-v2.3.0/ui/rollup.config.js similarity index 100% rename from game-template/ui/rollup.config.js rename to games/game1-v2.3.0/ui/rollup.config.js diff --git a/game-template/ui/src/Board.tsx b/games/game1-v2.3.0/ui/src/Board.tsx similarity index 96% rename from game-template/ui/src/Board.tsx rename to games/game1-v2.3.0/ui/src/Board.tsx index d7ec7fe..92e8f85 100644 --- a/game-template/ui/src/Board.tsx +++ b/games/game1-v2.3.0/ui/src/Board.tsx @@ -12,7 +12,7 @@ import { useUsername, } from "@lefun/ui"; -import type { RollGame, RollGameState } from "roll-game"; +import type { RollGame, RollGameState } from "game1-v2.3.0-game"; // Dice symbol characters const DICE = ["", "\u2680", "\u2681", "\u2682", "\u2683", "\u2684", "\u2685"]; diff --git a/game-template/ui/src/index.css b/games/game1-v2.3.0/ui/src/index.css similarity index 100% rename from game-template/ui/src/index.css rename to games/game1-v2.3.0/ui/src/index.css diff --git a/game-template/ui/src/index.ts b/games/game1-v2.3.0/ui/src/index.ts similarity index 100% rename from game-template/ui/src/index.ts rename to games/game1-v2.3.0/ui/src/index.ts diff --git a/game-template/ui/src/locales/en/messages.po b/games/game1-v2.3.0/ui/src/locales/en/messages.po similarity index 100% rename from game-template/ui/src/locales/en/messages.po rename to games/game1-v2.3.0/ui/src/locales/en/messages.po diff --git a/game-template/ui/src/locales/fr/messages.po b/games/game1-v2.3.0/ui/src/locales/fr/messages.po similarity index 100% rename from game-template/ui/src/locales/fr/messages.po rename to games/game1-v2.3.0/ui/src/locales/fr/messages.po diff --git a/game-template/ui/src/main.tsx b/games/game1-v2.3.0/ui/src/main.tsx similarity index 91% rename from game-template/ui/src/main.tsx rename to games/game1-v2.3.0/ui/src/main.tsx index ce1792d..6504df9 100644 --- a/game-template/ui/src/main.tsx +++ b/games/game1-v2.3.0/ui/src/main.tsx @@ -1,6 +1,6 @@ import { render } from "@lefun/dev-server"; -import { game } from "roll-game"; +import { game } from "game1-v2.3.0-game"; // @ts-expect-error abc import { messages as en } from "./locales/en/messages"; diff --git a/game-template/ui/tsconfig.json b/games/game1-v2.3.0/ui/tsconfig.json similarity index 100% rename from game-template/ui/tsconfig.json rename to games/game1-v2.3.0/ui/tsconfig.json diff --git a/game-template/ui/vite.config.ts b/games/game1-v2.3.0/ui/vite.config.ts similarity index 100% rename from game-template/ui/vite.config.ts rename to games/game1-v2.3.0/ui/vite.config.ts diff --git a/packages/core/package.json b/packages/core/package.json index 223c31a..e498cb1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,7 +35,7 @@ "rollup": "^2.79.1", "rollup-plugin-typescript2": "^0.31.2", "tslib": "^2.6.3", - "typescript": "^5.5.3", - "vitest": "^1.6.0" + "typescript": "^5.5.4", + "vitest": "^2.0.5" } } diff --git a/packages/core/src/meta.ts b/packages/core/src/meta.ts index 1bb2550..9a41464 100644 --- a/packages/core/src/meta.ts +++ b/packages/core/src/meta.ts @@ -1,5 +1,4 @@ -import { getRanks } from "./scores"; -import { Locale, Scores, ScoreType, UserId } from "./types"; +import { Locale, UserId } from "./types"; export interface Players { // List of all the player ids for the match. @@ -15,13 +14,6 @@ export type MatchPlayersSettings = Record; export type MatchSettings = Record; -export type EndMatchOptions = { - // When there is one score for the whole match. - score?: number; - // For cases where there is one score per player. - scores?: Scores; -}; - /* * Player info that is common to all games. A Player is a User in a Match. */ @@ -76,14 +68,6 @@ export type Meta = { locale?: Locale; }; -export type ItsYourTurnPayload = { - // Start the turn of these users. (defaults to []). - userIds?: UserId[] | "all"; - // End the turn of these users (defaults to 'all' IF `userIds` is defined; when both - // `userIds` and `overUserIds` are undefined, we don't change the turns.). - overUserIds?: UserId[] | "all"; -}; - export const metaInitialState = ({ matchSettings = {}, locale, @@ -162,91 +146,29 @@ export const metaRemoveUserFromMatch = (meta: Meta, userId: UserId): void => { meta.players.allIds.splice(idx, 1); }; -/* Update `meta` inplace at the end of a match. - */ -export const metaMatchEnded = ( - meta: Meta, - endMatchOptions: EndMatchOptions, - playerScoreType?: ScoreType, -): void => { - const { score, scores } = endMatchOptions; - - if (score != null) { - meta.score = score; - } - - if (scores != null) { - if (!playerScoreType) { - throw new Error('the "playerScoreType" must be provided with "scores"'); - } - const ranks = getRanks({ scores, scoreType: playerScoreType }); - - Object.entries(scores).forEach(([userId, score]) => { - const playerInfo = meta.players.byId[userId]; - if (playerInfo == null) { - console.warn( - `Trying to set the score for user ${userId} who is not in "meta"`, - ); - } else { - playerInfo.score = score; - const rank = ranks[userId]; - if (rank !== undefined) { - playerInfo.rank = rank; - } - } - }); - } -}; - -const setPlayerTurn = (meta: Meta, userId: UserId, value: boolean) => { - const player = meta.players.byId[userId]; - if (!player) { - console.warn( - `Trying to set itsYourTurn for user ${userId} who is not in "meta"`, - ); - return; - } - player.itsYourTurn = value; -}; - -export const metaItsYourTurn = ( - meta: Meta, - itsYourTurn: ItsYourTurnPayload, -): void => { - let { userIds, overUserIds } = itsYourTurn; - - // Deal with default values. - if (userIds === undefined && overUserIds === undefined) { - // Nothing to do if neither `userIds` nor `overUserIds` is defined. - return; - } - - // At this point at least one of `userIds` and `overUserIds` is defined. - - if (userIds === undefined) { - // If only `overUserIds` is defined, we don't start the turn of any player. - userIds = []; - } - - if (overUserIds === undefined) { - // If only `userIds` is defined, we end everyone's else's turns. - overUserIds = "all"; - } - - // End turns. - if (overUserIds === "all") { - overUserIds = meta.players.allIds; - } - - for (const userId of overUserIds) { - setPlayerTurn(meta, userId, false); - } - - // Start turns. +export const metaSetTurns = ({ + meta, + userIds, + value, +}: { + meta: Meta; + userIds: UserId | UserId[] | "all"; + value: boolean; +}): void => { if (userIds === "all") { userIds = meta.players.allIds; + } else if (!Array.isArray(userIds)) { + userIds = [userIds]; } + for (const userId of userIds) { - setPlayerTurn(meta, userId, true); + const player = meta.players.byId[userId]; + if (!player) { + console.warn( + `Trying to set itsYourTurn for user ${userId} who is not in "meta"`, + ); + continue; + } + player.itsYourTurn = value; } }; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 734e051..868cf16 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -8,9 +8,6 @@ export type UserId = string; export type Locale = "fr" | "en"; export const LOCALES: Locale[] = ["fr", "en"]; -export type Scores = Record; -export type ScoreType = "seconds" | "rank" | "integer"; - /* * We have some game definition types here, namely the ones used not only in the game * but also on the website. @@ -107,11 +104,55 @@ export type GamePlayerSetting = { // "gamePlayerSettings" that we store. export type GamePlayerSettings = Record; -export type GameStat = { - key: string; +export type GameStatType = + | "integer" + | "rank" + | "seconds" + | "boolean" + // integer or float + | "number"; + +type StatKey = string; + +export type GameStat = { + key: K; + type: GameStatType; + + // Defaults to `higherIsBetter`. + ordering?: "higherIsBetter" | "lowerIsBetter"; + + determinesRank?: boolean; + // Should we use this stat to make a leaderboard? Defaults to `false`. + // Note that currently we assume that this can only apply to *match* stats. + leaderboard?: boolean; }; + +export type GameStat_ = Omit< + Required, + "determinesRank" | "leaderboard" +>; + export type GameStats = GameStat[]; +export type GameStats_ = { + allIds: StatKey[]; + byId: Record; + // We use an array because I feel that later we'll want multiple leaderboards for + // different scores (e.g. 3bv in Eggsplosion) but currently we only use the first + // one for a single leaderboard per game. + leaderboard: StatKey[]; + determinesRank: StatKey[]; +}; + export type Credits = { design?: string[]; }; + +// Some conditional type utilities. +export type IfNull = [T] extends [null] ? Y : N; +export type IfAny = 0 extends 1 & T ? Y : N; +export type IfAnyNull = IfAny< + T, + ANY, + IfNull +>; diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 447597f..da62d47 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -56,8 +56,8 @@ "rollup": "^4.18.1", "rollup-plugin-postcss": "^4.0.2", "tailwindcss": "^3.4.6", - "typescript": "^5.5.3", - "vitest": "^1.6.0" + "typescript": "^5.5.4", + "vitest": "^2.0.5" }, "peerDependencies": { "@lefun/core": "workspace:*", diff --git a/packages/dev-server/src/App.tsx b/packages/dev-server/src/App.tsx index 5b4ed18..23f1fcd 100644 --- a/packages/dev-server/src/App.tsx +++ b/packages/dev-server/src/App.tsx @@ -11,7 +11,7 @@ import { createRoot } from "react-dom/client"; import { createStore as _createStore } from "zustand"; import type { Locale, MatchSettings, UserId, UsersState } from "@lefun/core"; -import { Game } from "@lefun/game"; +import { Game, MoveSideEffects } from "@lefun/game"; import { setMakeMove, Store, storeContext } from "@lefun/ui"; import { @@ -111,6 +111,31 @@ const BoardForPlayer = ({ } } + const sideEffects: MoveSideEffects = { + delayMove() { + console.warn("delayMove not implemented yet"); + return { ts: 0 }; + }, + endMatch() { + // + }, + logPlayerStat() { + // + }, + logMatchStat() { + // + }, + turns: { + begin() { + console.warn("turns.begin not implemented"); + return { expiresAt: 0 }; + }, + end() { + // + }, + }, + }; + if (executeNow) { // Optimistic update directly on the `store` of the player making the move. store.setState((state: MatchState) => { @@ -121,10 +146,8 @@ const BoardForPlayer = ({ board, playerboard, payload, - delayMove: () => { - console.warn("delayMove not implemented yet"); - return { ts: 0 }; - }, + _: sideEffects, + ...sideEffects, }); }); return newState; diff --git a/packages/dev-server/src/match.ts b/packages/dev-server/src/match.ts index 5f7ad27..8244a03 100644 --- a/packages/dev-server/src/match.ts +++ b/packages/dev-server/src/match.ts @@ -8,7 +8,7 @@ import { User, UserId, } from "@lefun/core"; -import { Game, GameStateBase, Random } from "@lefun/game"; +import { Game, GameStateBase, MoveSideEffects, Random } from "@lefun/game"; type State = { board: unknown; @@ -107,6 +107,7 @@ class Match extends EventTarget { random, areBots, locale, + ts: new Date().getTime(), }); const { board, secretboard = {} } = initialBoards; @@ -154,6 +155,31 @@ class Match extends EventTarget { ); patchesByUserId["spectator"] = []; + const sideEffects: MoveSideEffects = { + delayMove() { + console.warn("delayMove not implemented yet"); + return { ts: 0 }; + }, + endMatch() { + console.warn("endMatch not implemented"); + }, + logPlayerStat() { + console.warn("logPlayerStat not implemented"); + }, + logMatchStat() { + console.warn("logMatchStat not implemented"); + }, + turns: { + begin() { + console.warn("turns.begin not implemented"); + return { expiresAt: 0 }; + }, + end() { + console.warn("turns.end not implemented"); + }, + }, + }; + if (executeNow) { // Also run `executeNow` on the local state. this.store.setState((state: State) => { @@ -169,10 +195,8 @@ class Match extends EventTarget { userId, board, playerboard: playerboards[userId], - delayMove: () => { - console.warn("delayMove not implemented yet"); - return { ts: 0 }; - }, + _: sideEffects, + ...sideEffects, }); }, ); @@ -210,19 +234,8 @@ class Match extends EventTarget { gameData, random, ts: now, - delayMove: () => { - console.warn("delayMove not implemented yet"); - return { ts: 0 }; - }, - endMatch: () => { - console.warn("todo implement endMatch"); - }, - itsYourTurn: () => { - console.warn("todo implement itsYourTurn"); - }, - logStat: () => { - console.warn("todo implement logStats"); - }, + _: sideEffects, + ...sideEffects, }); }, ); diff --git a/packages/game/package.json b/packages/game/package.json index a408485..c5e1ddd 100644 --- a/packages/game/package.json +++ b/packages/game/package.json @@ -37,8 +37,8 @@ "rollup": "^2.79.1", "rollup-plugin-typescript2": "^0.31.2", "tslib": "^2.6.3", - "typescript": "^5.5.3", - "vitest": "^1.6.0" + "typescript": "^5.5.4", + "vitest": "^2.0.5" }, "peerDependencies": { "@lefun/core": "workspace:*" diff --git a/packages/game/src/gameDef.test-d.ts b/packages/game/src/gameDef.test-d.ts new file mode 100644 index 0000000..b283afe --- /dev/null +++ b/packages/game/src/gameDef.test-d.ts @@ -0,0 +1,318 @@ +import { describe, expectTypeOf, test } from "vitest"; + +import { + AutoMove, + BoardMove, + Game, + GameState, + GetPayload, + PlayerMove, +} from "./gameDef"; + +test("PlayerMove - fully-typed", () => { + type B = { + x: number; + }; + + type GS = GameState; + + type PMT = { + move1: null; + move2: { a: number }; + }; + + type BMT = { + boardMove: { payload: string }; + }; + + type Payload = { y: string }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const payloadMove: PlayerMove = { + executeNow({ board, payload, delayMove, turns }) { + expectTypeOf(board).toMatchTypeOf<{ x: number }>(); + expectTypeOf(payload).toMatchTypeOf<{ y: string }>(); + + // + // delayMove + + // @ts-expect-error missing payload + delayMove("boardMove", 1000); + + // @ts-expect-error wrong name + delayMove("boardMove2", 1000); + + delayMove("boardMove", { payload: "patate" }, 123); + + // + // turns.begin + + turns.begin("userId", { + expiresIn: 1000, + // @ts-expect-error missing payload + boardMoveOnExpire: "boardMove", + }); + + turns.begin("userId", { + expiresIn: 1000, + // @ts-expect-error wrong arg + boardMoveOnExpire: ["boardMove", { patate: 123 }], + }); + + turns.begin("userId", { + expiresIn: 1000, + boardMoveOnExpire: ["boardMove", { payload: "patate" }], + }); + + turns.begin("userId", { + expiresIn: 1000, + // @ts-expect-error wrong move name + playerMoveOnExpire: "patate", + }); + + turns.begin("userId", { + expiresIn: 1000, + // @ts-expect-error extra arg + playerMoveOnExpire: ["move1", { a: 2 }], + }); + + turns.begin("userId", { + expiresIn: 1000, + playerMoveOnExpire: "move1", + }); + + turns.begin("userId", { + expiresIn: 1000, + // @ts-expect-error missing arg + playerMoveOnExpire: "move2", + }); + + turns.begin("userId", { + expiresIn: 1000, + // @ts-expect-error wrong arg + playerMoveOnExpire: ["move2", { a: "abc" }], + }); + + turns.begin("userId", { + expiresIn: 1000, + playerMoveOnExpire: ["move2", { a: 123 }], + }); + }, + }; +}); + +test("PlayerMove - payload-typed", () => { + type B = { + x: number; + }; + + type GS = GameState; + + type Payload = { y: string }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const payloadMove: PlayerMove = { + executeNow({ board, payload, delayMove, turns }) { + expectTypeOf(board).toMatchTypeOf<{ x: number }>(); + expectTypeOf(payload).toMatchTypeOf<{ y: string }>(); + + delayMove("boardMove", 1000); + delayMove("boardMove", { payload: "patate" }, 123); + + // Everything goes when we don't have PMT/BMT typing. + turns.begin("userId", { expiresIn: 123 }); + turns.begin("userId", { expiresIn: 123, playerMoveOnExpire: "move1" }); + turns.begin("userId", { + expiresIn: 123, + playerMoveOnExpire: ["move1", { arg: 123 }], + }); + turns.begin("userId", { expiresIn: 123, boardMoveOnExpire: "move2" }); + turns.begin("userId", { + expiresIn: 123, + boardMoveOnExpire: ["move2", { arg: 123 }], + }); + }, + }; +}); + +test("PlayerMove inside game - only payload typed", () => { + type B = { + x: number; + }; + + type GS = GameState; + + type Payload = { y: string }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const payloadMove: PlayerMove = { + executeNow({ board, payload }) { + expectTypeOf(board).toMatchTypeOf<{ x: number }>(); + expectTypeOf(payload).toMatchTypeOf<{ y: string }>(); + }, + }; +}); + +test("todo", () => { + type GS = GameState<{ + x: number; + }>; + + type PMT = { + move: null; + }; + + type BMT = { + boardMove: null; + }; + + const move: PlayerMove = {}; + const boardMove: BoardMove = {}; + + const game = { + initialBoards: () => ({ board: { x: 0 } }), + playerMoves: { move }, + boardMoves: { boardMove }, + minPlayers: 1, + maxPlayers: 1, + } satisfies Game; + + type G = typeof game; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const autoMove: AutoMove = () => { + return "move"; + }; +}); + +// Sanity checks. +describe("GetPayload", () => { + test("if game is default, payload is any", () => { + type G = Game; + + expectTypeOf>().toBeAny(); + }); + + test("if game is defined, payload is correct", () => { + type GS = GameState<{ + a: number; + }>; + + const move1: PlayerMove = { + // + }; + const move2: PlayerMove = { + // + }; + const game = { + initialBoards: () => ({ board: { a: 0 } }), + playerMoves: { + move1, + move2, + }, + minPlayers: 1, + maxPlayers: 1, + } satisfies Game; + + type G = typeof game; + + expectTypeOf>().toBeNull(); + expectTypeOf>().toEqualTypeOf<{ x: number }>(); + }); +}); + +test("moves inlined in the game - GS is properly used", () => { + type GS = GameState<{ + a: number; + }>; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const game = { + initialBoards: () => ({ board: { a: 0 } }), + playerMoves: { + move: { + execute({ board }) { + expectTypeOf(board).toMatchTypeOf<{ a: number }>(); + }, + }, + }, + boardMoves: { + boardMove: { + execute({ board }) { + expectTypeOf(board).toMatchTypeOf<{ a: number }>(); + }, + }, + }, + minPlayers: 1, + maxPlayers: 1, + } satisfies Game; +}); + +test("moves inlined in the game with PMT and BMT - they are used", () => { + type GS = GameState<{ + a: number; + }>; + + type PMT = { + move1: null; + move2: { a: number }; + }; + + type BMT = { + boardMove1: null; + boardMove2: { a: number }; + }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const game = { + initialBoards: () => ({ board: { a: 0 } }), + playerMoves: { + move: { + execute({ board, delayMove, turns }) { + expectTypeOf(board).toMatchTypeOf<{ a: number }>(); + + delayMove("boardMove1", 1000); + + // @ts-expect-error extra arg + delayMove("boardMove1", { a: 123 }, 1000); + + // @ts-expect-error missing arg + delayMove("boardMove2", 1000); + + delayMove("boardMove2", { a: 123 }, 1000); + + turns.begin("userId", { + expiresIn: 123, + playerMoveOnExpire: "move1", + }); + + turns.begin("userId", { + expiresIn: 123, + // @ts-expect-error missing arg + playerMoveOnExpire: "move2", + }); + + turns.begin("userId", { + expiresIn: 123, + // @ts-expect-error extra arg + playerMoveOnExpire: ["move1", { a: 123 }], + }); + + turns.begin("userId", { + expiresIn: 123, + playerMoveOnExpire: ["move2", { a: 123 }], + }); + }, + }, + }, + boardMoves: { + boardMove: { + execute({ board }) { + expectTypeOf(board).toMatchTypeOf<{ a: number }>(); + }, + }, + }, + minPlayers: 1, + maxPlayers: 1, + } satisfies Game; +}); diff --git a/packages/game/src/gameDef.ts b/packages/game/src/gameDef.ts index e11f1c2..fbabaad 100644 --- a/packages/game/src/gameDef.ts +++ b/packages/game/src/gameDef.ts @@ -1,25 +1,24 @@ import { AkaType, Credits, - EndMatchOptions, GamePlayerSettings, GameSettings, - GameStats, - ItsYourTurnPayload, + GameStat, + GameStats_, + IfAny, + IfNull, Locale, MatchPlayerSettings, MatchPlayersSettings, MatchSettings, - ScoreType, UserId, } from "@lefun/core"; import { Random } from "./random"; -import { IfNever } from "./typing"; -export type GameStateBase = { B: unknown; PB: unknown; SB: unknown }; +export type GameStateBase = { B: any; PB: any; SB: any }; -type BMTBase = Record; +export type MoveTypesBase = Record; export type GameState = { B: B; @@ -27,10 +26,12 @@ export type GameState = { SB: SB; }; -type NoPayload = never; - type EmptyObject = Record; +export type GameStats = GameStat[]; + +// Special moves + export const INIT_MOVE = "lefun/initMove"; export const ADD_PLAYER = "lefun/addPlayer"; @@ -47,81 +48,172 @@ export const MATCH_WAS_ABORTED = "lefun/matchWasAborted"; export type RewardPayload = { rewards: Record; + // TODO Now that we have proper stats, we should use those instead. stats?: Record>; }; -export type DelayMove = ( +export type DelayMove = < + K extends keyof BMT & string, +>( moveName: K, - ...rest: IfNever + ...rest: IfAny< + BMT[K], + [number] | [any, number], + IfNull + > ) => { ts: number }; -export type SpecialFuncs = { +type ValueOf = T[keyof T]; + +export type Turns< + PMT extends MoveTypesBase = MoveTypesBase, + BMT extends MoveTypesBase = MoveTypesBase, +> = { + end: (userIds: UserId | UserId[] | "all") => void; + begin: ( + userIds: UserId | UserId[] | "all", + options?: { + expiresIn?: number; + boardMoveOnExpire?: IfAny< + ValueOf, + string | [string, any], + MoveObjFromMT + >; + playerMoveOnExpire?: IfAny< + ValueOf, + string | [string, any], + MoveObjFromMT + >; + }, + ) => { expiresAt: number | undefined }; +}; + +type StatsKeys = S extends { key: infer K }[] ? K : never; + +export type EndMatch = () => void; + +export type LogPlayerStat = ( + userId: UserId, + key: StatsKeys, + value: number, +) => void; + +export type LogMatchStat = ( + key: StatsKeys, + value: number, +) => void; + +export type MoveSideEffects< + PMT extends MoveTypesBase = MoveTypesBase, + BMT extends MoveTypesBase = MoveTypesBase, + PS extends GameStats = GameStats, + MS extends GameStats = GameStats, +> = { delayMove: DelayMove; - itsYourTurn: (arg0: ItsYourTurnPayload) => void; - endMatch: (arg0?: EndMatchOptions) => void; + turns: Turns; + endMatch: EndMatch; reward?: (options: RewardPayload) => void; - logStat: (key: string, value: number) => void; + logPlayerStat: LogPlayerStat; + logMatchStat: LogMatchStat; }; -type ExecuteNowOptions = { +type ExecuteNowOptions< + GS extends GameStateBase, + P, + PMT extends MoveTypesBase, + BMT extends MoveTypesBase, + PS extends GameStats, + MS extends GameStats, +> = { userId: UserId; board: GS["B"]; // Assume that the game developer has defined the playerboard if they're using it. playerboard: GS["PB"]; payload: P; - delayMove: SpecialFuncs["delayMove"]; -}; - -export type ExecuteNow = ( - options: ExecuteNowOptions, + _: MoveSideEffects; +} & MoveSideEffects; + +export type ExecuteNow< + GS extends GameStateBase = GameStateBase, + P = any, + PMT extends MoveTypesBase = MoveTypesBase, + BMT extends MoveTypesBase = MoveTypesBase, + PS extends GameStats = GameStats, + MS extends GameStats = GameStats, +> = ( + options: ExecuteNowOptions, // TODO: We should support returning anything and it would be passed to `execute`. ) => void | false; -export type ExecuteOptions = { +export type ExecuteOptions< + GS extends GameStateBase, + P, + PMT extends MoveTypesBase, + BMT extends MoveTypesBase, + PS extends GameStats, + MS extends GameStats, +> = { userId: UserId; - board: G["B"]; + board: GS["B"]; // Even though `playerboards` and `secretboard` are optional, we'll assume that the // game developer has defined them if they use them if their execute* functions! - playerboards: Record; - secretboard: G["SB"]; + playerboards: Record; + secretboard: GS["SB"]; payload: P; random: Random; ts: number; gameData: any; matchData?: any; -} & SpecialFuncs; - -export type Execute = ( - options: ExecuteOptions, -) => void; + _: MoveSideEffects; +} & MoveSideEffects; + +export type Execute< + GS extends GameStateBase = GameStateBase, + P = any, + PMT extends MoveTypesBase = MoveTypesBase, + BMT extends MoveTypesBase = MoveTypesBase, + PS extends GameStats = GameStats, + MS extends GameStats = GameStats, +> = (options: ExecuteOptions) => void; export type PlayerMove< - G extends GameStateBase, - P = NoPayload, - BMT extends BMTBase = EmptyObject, + GS extends GameStateBase = GameStateBase, + P = any, + PMT extends MoveTypesBase = MoveTypesBase, + BMT extends MoveTypesBase = MoveTypesBase, + PS extends GameStats = GameStats, + MS extends GameStats = GameStats, > = { canDo?: (options: { userId: UserId; - board: G["B"]; - playerboard: G["PB"]; + board: GS["B"]; + playerboard: GS["PB"]; payload: P; // We'll pass `null` on the client, where we don't have the server time. ts: number | null; }) => boolean; - executeNow?: ExecuteNow; - execute?: Execute; + executeNow?: ExecuteNow; + execute?: Execute; }; -export type BoardExecute = ( - options: Omit, "userId">, -) => void; +export type BoardExecute< + GS extends GameStateBase = GameStateBase, + P = any, + PMT extends MoveTypesBase = MoveTypesBase, + BMT extends MoveTypesBase = MoveTypesBase, + PS extends GameStats = GameStats, + MS extends GameStats = GameStats, +> = (options: Omit, "userId">) => void; export type BoardMove< - G extends GameStateBase, - P = NoPayload, - BMT extends BMTBase = EmptyObject, + GS extends GameStateBase = GameStateBase, + P = any, + PMT extends MoveTypesBase = MoveTypesBase, + BMT extends MoveTypesBase = MoveTypesBase, + PS extends GameStats = GameStats, + MS extends GameStats = GameStats, > = { - execute?: BoardExecute; + execute?: BoardExecute; }; export type InitialBoardsOptions = { @@ -137,19 +229,20 @@ export type InitialBoardsOptions = { // This is used occasionnaly for games with localized data where we don't want to // add an gameSetting. locale: Locale; + // Timestamp at which the match is started. + ts: number; }; -export type InitialPlayerboardOptions = { +export type InitialPlayerboardOptions = { userId: UserId; - board: G["B"]; - secretboard: G["SB"]; + board: GS["B"]; + secretboard: GS["SB"]; // Those are the playerboards for the *other* players. - playerboards: Record; + playerboards: Record; random: Random; gameData: any; matchData?: any; }; - /* * Object that `autoMove` can return to help train reinforcement learning models. */ @@ -185,26 +278,33 @@ export type AutoMoveInfo = { }; export type GetPayload< - G extends Game, + G extends Game, K extends keyof G["playerMoves"] & string, -> = +> = IfAny< + ValueOf, + any, // eslint-disable-next-line @typescript-eslint/no-unused-vars - G["playerMoves"][K] extends PlayerMove + G["playerMoves"][K] extends PlayerMove ? P - : never; + : never +>; /* * `name: string` if the move doesn't have any payload, [name: string, payload: ?] * otherwise. */ -type MoveObj> = { - [K in keyof G["playerMoves"] & string]: IfNever< +export type MoveObj = { + [K in keyof G["playerMoves"] & string]: IfNull< GetPayload, K, [K, GetPayload] >; }[keyof G["playerMoves"] & string]; +type MoveObjFromMT = { + [K in keyof MT & string]: IfNull; +}[keyof MT & string]; + /* * Stateless function that returns a bot move. */ @@ -260,40 +360,32 @@ export type GetMatchScoreTextOptions = { board: B; }; +export type InitialBoard = ( + options: InitialBoardsOptions, +) => { + board: GS["B"]; + playerboards?: Record; + secretboard?: GS["SB"]; +}; + // This is what the game developer must implement. export type Game< - GS extends GameStateBase, - BMT extends BMTBase = EmptyObject, - PM extends Record> = any, - BM extends Record> = any, + GS extends GameStateBase = GameStateBase, + PMT extends MoveTypesBase = any, + BMT extends MoveTypesBase = any, > = { - initialBoards: (options: InitialBoardsOptions) => { - board: GS["B"]; - playerboards?: Record; - secretboard?: GS["SB"]; - itsYourTurnUsers?: UserId[]; - }; + initialBoards: InitialBoard; // There is a separate function for the `playerboard` for games that support adding a // player into an ongoing match. initialPlayerboard?: (options: InitialPlayerboardOptions) => GS["PB"]; // For those the key is the `name` of the move/update - playerMoves: PM; - boardMoves?: BM; + playerMoves: Record>; + boardMoves?: Record>; gameSettings?: GameSettings; gamePlayerSettings?: GamePlayerSettings; - // Do we have `score`s for this game? This means for instance that we can show - // a leaderboard. - - // Type of the match score. This is when there is only one score for the match. - // This tells us how to format it. A defined matchScoreType also means we can have a leaderboard. - matchScoreType?: ScoreType; - - // Type of the player score. This is when there is only one score per player. - playerScoreType?: ScoreType; - // Games can customize the match score representation using this hook. getMatchScoreText?: (options: GetMatchScoreTextOptions) => string; @@ -316,7 +408,8 @@ export type Game< // using a search param in the URL. useInitialLocale?: boolean; - stats?: GameStats; + playerStats?: GameStats; + matchStats?: GameStats; }; /* @@ -324,39 +417,68 @@ export type Game< * * When developing a game, use `Game` instead. */ -export type Game_ = Omit< - Game, - "stats" -> & { - stats?: { - allIds: string[]; - // Note that we don't have any flags for stats yet, hence the `EmptyObject`. - byId: Record; - }; +export type Game_< + GS extends GameStateBase = GameStateBase, + PMT extends MoveTypesBase = MoveTypesBase, + BMT extends MoveTypesBase = MoveTypesBase, +> = Omit, "playerStats" | "matchStats"> & { + playerStats?: GameStats_; + matchStats?: GameStats_; }; -/* - * Parse a @lefun/game game definition into our internal game definition. - */ -export function parseGame( - game: Game, -): Game_ { - // Normalize the stats. - const { stats: stats_ } = game; - - const stats: Game_["stats"] = { - allIds: [], - byId: {}, +function normalizeArray, K extends keyof T>( + arr: T[], + key: K, +): { allIds: T[K][]; byId: Record } { + const allIds = arr.map((item) => item[key]); + const byId = Object.fromEntries(arr.map((item) => [item[key], item])); + return { allIds, byId }; +} + +/* Reorganize the stats to make it easier to work with. */ +function normalizeStats(stats: GameStats | undefined): GameStats_ { + if (stats === undefined) { + return { allIds: [], byId: {}, leaderboard: [], determinesRank: [] }; + } + + const { allIds, byId } = normalizeArray(stats, "key"); + const stats_: GameStats_ = { + allIds, + // Remove redondant keys. + byId: Object.fromEntries( + allIds.map((key) => { + const { type, ordering = "higherIsBetter" } = byId[key]; + return [key, { key, type, ordering }]; + }), + ), + leaderboard: [], + determinesRank: [], }; - if (stats_) { - for (const { key } of stats_) { - stats.allIds.push(key); - stats.byId[key] = {}; + for (const { key, leaderboard, determinesRank } of stats || []) { + if (leaderboard) { + stats_.leaderboard.push(key); + } + if (determinesRank) { + stats_.determinesRank.push(key); } } - return { ...game, stats }; + return stats_; +} + +/* + * Parse a @lefun/game game definition into our internal game definition. + */ +export function parseGame< + GS extends GameStateBase, + PMT extends MoveTypesBase, + BMT extends MoveTypesBase, +>(game: Game): Game_ { + const playerStats = normalizeStats(game.playerStats); + const matchStats = normalizeStats(game.matchStats); + + return { ...game, playerStats, matchStats }; } // Game Manifest diff --git a/packages/game/src/index.ts b/packages/game/src/index.ts index 5444f91..bcc01a0 100644 --- a/packages/game/src/index.ts +++ b/packages/game/src/index.ts @@ -1,3 +1,4 @@ export * from "./gameDef"; export * from "./random"; export * from "./testing"; +export * from "./utils"; diff --git a/packages/game/src/testing.test-d.ts b/packages/game/src/testing.test-d.ts new file mode 100644 index 0000000..1db71f5 --- /dev/null +++ b/packages/game/src/testing.test-d.ts @@ -0,0 +1,42 @@ +import { describe, test } from "vitest"; + +import { Game, GameState, PlayerMove } from "."; +import { MatchTester } from "./testing"; + +describe("MatchTester", () => { + test("makeMove", () => { + type GS = GameState<{ + x: number; + }>; + + const move1: PlayerMove = { + executeNow({ board, payload }) { + board.x += payload.a; + }, + }; + + const game = { + playerMoves: { move1 }, + initialBoards: () => ({ board: { x: 0 } }), + minPlayers: 1, + maxPlayers: 10, + } satisfies Game; + + type G = typeof game; + + const match = new MatchTester({ game, numPlayers: 1 }); + + const userId = "userId"; + + // @ts-expect-error missing arg + match.makeMove(userId, "move1"); + + // @ts-expect-error wrong arg key + match.makeMove(userId, "move1", { b: 3 }); + + // @ts-expect-error wrong arg value type + match.makeMove(userId, "move1", { a: "abc" }); + + match.makeMove(userId, "move1", { a: 3 }); + }); +}); diff --git a/packages/game/src/testing.test.ts b/packages/game/src/testing.test.ts new file mode 100644 index 0000000..df32d6d --- /dev/null +++ b/packages/game/src/testing.test.ts @@ -0,0 +1,230 @@ +import { describe, expect, test } from "vitest"; + +import { BoardMove, Game, GameState, INIT_MOVE, PlayerMove } from "."; +import { MatchTester } from "./testing"; + +const gameBase = { + minPlayers: 1, + maxPlayers: 10, +}; + +describe("turns", () => { + type B = { + me: number; + bot: number; + numZeroDelay: number; + expiresAt: number | undefined; + }; + type GS = GameState; + type P = { itWasMe?: boolean }; + type PM

= PlayerMove; + + const game = { + ...gameBase, + initialBoards: ({ players }) => ({ + board: { + me: 0, + bot: 0, + numZeroDelay: 0, + expiresAt: undefined, + }, + itsTheirTurn: players, + }), + playerMoves: { + go: { + execute({ userId, board, payload, turns }) { + if (payload.itWasMe ?? true) { + board.me++; + } else { + board.bot++; + } + + const { expiresAt } = turns.begin(userId, { + expiresIn: 1000, + playerMoveOnExpire: ["go", { itWasMe: false }], + }); + board.expiresAt = expiresAt; + }, + } as PM

, + endTurn: { + execute({ userId, turns }) { + turns.end(userId); + }, + } as PM, + beginWithDelay: { + execute({ board, userId, turns, endMatch, payload }) { + const { delay } = payload; + board.numZeroDelay++; + if (board.numZeroDelay >= 10) { + endMatch(); + return; + } + const { expiresAt } = turns.begin(userId, { + expiresIn: delay, + playerMoveOnExpire: ["beginWithDelay", { delay: 0 }], + }); + board.expiresAt = expiresAt; + }, + } as PM<{ delay: number }>, + move: { + execute() { + // + }, + } as PM, + }, + } satisfies Game; + + type G = typeof game; + + test("playerMoveOnExpire", () => { + const match = new MatchTester({ game, numPlayers: 1 }); + + const userId = match.meta.players.allIds[0]; + expect(match.board.expiresAt).toBe(undefined); + + // I make a move + match.makeMove(userId, "go", {}); + expect(match.board.expiresAt).toEqual(1000); + + // Auto move 1 second later + match.fastForward(1000); + + expect(match.board.me).toBe(1); + expect(match.board.bot).toBe(1); + + // Other automove 1 second later + match.fastForward(1000); + + expect(match.board.me).toBe(1); + expect(match.board.bot).toBe(2); + + // I move before the auto-move. The timer resets. + match.fastForward(500); + match.makeMove(userId, "go", {}); + + expect(match.board.me).toBe(2); + expect(match.board.bot).toBe(2); + + // So this is not enough. + match.fastForward(500); + + expect(match.board.me).toBe(2); + expect(match.board.bot).toBe(2); + + // Auto move triggered 1s after the last move. + match.fastForward(500); + + expect(match.board.me).toBe(2); + expect(match.board.bot).toBe(3); + + match.fastForward(1000); + + expect(match.board.me).toBe(2); + expect(match.board.bot).toBe(4); + + // Finally test that if we simply end the player's turn, the timers reset. + match.fastForward(500); + match.makeMove(userId, "endTurn"); + + expect(match.board.me).toBe(2); + expect(match.board.bot).toBe(4); + + match.fastForward(8000); + + expect(match.board.me).toBe(2); + expect(match.board.bot).toBe(4); + }); + + test("executes moves with delay 0 right away", () => { + const match = new MatchTester({ game, numPlayers: 1 }); + const userId = match.meta.players.allIds[0]; + match.makeMove(userId, "beginWithDelay", { delay: 0 }); + expect(match.board.numZeroDelay).toBe(10); + expect(match.matchHasEnded).toBe(true); + }); + + test("executes moves with delay 1", () => { + const match = new MatchTester({ game, numPlayers: 1 }); + const userId = match.meta.players.allIds[0]; + match.makeMove(userId, "beginWithDelay", { delay: 1 }); + match.fastForward(1); + expect(match.board.numZeroDelay).toBe(10); + expect(match.matchHasEnded).toBe(true); + }); + + test("expiresAt", () => { + const match = new MatchTester({ game, numPlayers: 1 }); + + const userId = match.meta.players.allIds[0]; + expect(match.board.expiresAt).toBe(undefined); + + match.makeMove(userId, "go", {}); + expect(match.board.expiresAt).toEqual(1000); + }); +}); + +describe("fastForward", () => { + test("fastForward takes into account the time of triggering", () => { + // Let's say we fastFoward(10 seconds), but at 5 seconds something would trigger + // Something 1 second later, it has to execute at 6 seconds. + type B = { + timestamps: number[]; + }; + type GS = GameState; + const game = { + ...gameBase, + initialBoards: () => ({ board: { timestamps: [] } }), + playerMoves: {}, + boardMoves: { + [INIT_MOVE]: { + execute({ delayMove }) { + delayMove("move", 0); + }, + }, + move: { + execute({ board, ts, delayMove }) { + board.timestamps.push(ts); + if (board.timestamps.length <= 3) { + delayMove("move", 1000); + } + }, + } satisfies BoardMove, + }, + } satisfies Game; + + const match = new MatchTester({ game, numPlayers: 1 }); + + match.fastForward(5000); + expect(match.board.timestamps).toEqual([0, 1000, 2000, 3000]); + }); + + test("add move in reverse order", () => { + type B = { + timestamps: number[]; + }; + type GS = GameState; + const game = { + ...gameBase, + initialBoards: () => ({ board: { timestamps: [] } }), + playerMoves: {}, + boardMoves: { + [INIT_MOVE]: { + execute({ delayMove }) { + delayMove("move", 1000); + delayMove("move", 0); + }, + }, + move: { + execute({ board, ts }) { + board.timestamps.push(ts); + }, + } satisfies BoardMove, + }, + } satisfies Game; + + const match = new MatchTester({ game, numPlayers: 1 }); + + match.fastForward(5000); + expect(match.board.timestamps).toEqual([0, 1000]); + }); +}); diff --git a/packages/game/src/testing.ts b/packages/game/src/testing.ts index 49efe0f..43baa7d 100644 --- a/packages/game/src/testing.ts +++ b/packages/game/src/testing.ts @@ -1,17 +1,14 @@ import { - EndMatchOptions, - ItsYourTurnPayload, Locale, MatchPlayerSettings, MatchSettings, Meta, metaAddUserToMatch, metaInitialState, - metaItsYourTurn, - metaMatchEnded, metaRemoveUserFromMatch, UserId, } from "@lefun/core"; +import { IfAnyNull } from "@lefun/core"; import { ADD_PLAYER, @@ -19,6 +16,7 @@ import { AutoMove, AutoMoveInfo, BotMove, + DelayMove, Game, Game_, GameStateBase, @@ -30,22 +28,43 @@ import { parseBotMove, parseGame, RewardPayload, + Turns, } from "./gameDef"; import { Random } from "./random"; -import { IfNever } from "./typing"; +import { parseMove, parseTurnUserIds } from "./utils"; +type DelayedPlayerMove = { + type: "playerMove"; + name: string; + payload: any; + ts: number; + userId: UserId; +}; + +// The main different with DelayedPlayerMove is that userId is optional. type DelayedBoardMove = { + type: "boardMove"; name: string; payload: any; ts: number; + // We still need the `userId` when it's a move that is triggered when the player's turn expires. + userId?: UserId; }; -type MatchTesterOptions> = { - game: Game; +type DelayedMove = DelayedBoardMove | DelayedPlayerMove; + +export type MatchTesterOptions< + GS extends GameStateBase, + G extends Game, //, PMT, BMT>, + // PMT extends MoveTypesBase = MoveTypesBase, + // BMT extends MoveTypesBase = MoveTypesBase, +> = { + game: G; getAgent?: GetAgent; autoMove?: AutoMove; gameData?: any; matchData?: any; + // Num *human* players numPlayers: number; numBots?: number; matchSettings?: MatchSettings; @@ -76,15 +95,19 @@ type User = { type UsersState = { byId: Record }; +type MakeMoveOptions = { canFail?: boolean; isDelayed?: boolean }; + type MakeMoveRest< - G extends Game, + G extends Game, K extends keyof G["playerMoves"] & string, -> = IfNever< +> = IfAnyNull< GetPayload, - // - [] | [EmptyObject, { canFail?: boolean }], - // - [GetPayload] | [GetPayload, { canFail?: boolean }] + // any + [] | [any] | [any, MakeMoveOptions], + // null + [] | [EmptyObject, MakeMoveOptions], + // other + [GetPayload] | [GetPayload, MakeMoveOptions] >; /* @@ -93,10 +116,11 @@ type MakeMoveRest< */ export class MatchTester< GS extends GameStateBase, - G extends Game, - BMT extends Record = any, + G extends Game, //, PMT, BMT>, + // PMT extends MoveTypesBase = MoveTypesBase, + // BMT extends MoveTypesBase = MoveTypesBase, > { - game: Game_; + game: Game_; //, PMT, BMT>; autoMove?: AutoMove; getAgent?: GetAgent; gameData: any; @@ -113,7 +137,13 @@ export class MatchTester< // Clock used for delayedMoves - in ms. time: number; // List of timers to be executed. - delayedMoves: DelayedBoardMove[]; + delayedMoves: DelayedMove[]; + // List of end of turn player moves. + // endOfTurnPlayerMoves: Record; + + // List of end of turn board moves. + // endOfTurnBoardMoves: Record; + // To help generate the next userIds. nextUserId: number; // Variables to check for infinite loops. @@ -122,13 +152,19 @@ export class MatchTester< _botTrainingLog: BotTrainingLogItem[]; _verbose: boolean; _logBoardToTrainingLog: boolean; - _stats: { key: string; value: number }[]; // Are we using the MatchTester for training. // TODO We should probably use different classes for training and for testing. _training: boolean; _agents: Record>; _isPlaying: boolean; + playerStats: Record; + matchStats: { key: string; value: number }[]; + + // This is hacky. We need some place to store the userIds of the players whose turn + // begins after a move. + _lastTurnsBegin: Set; + constructor({ game, autoMove, @@ -232,12 +268,9 @@ export class MatchTester< ]), ); - const { - board, - playerboards = {}, - secretboard = {} as GS["SB"], - itsYourTurnUsers = [], - } = game.initialBoards({ + const time = 0; + + const init = game.initialBoards({ players: meta.players.allIds, matchSettings, matchPlayersSettings, @@ -247,11 +280,10 @@ export class MatchTester< areBots, // What about `previousBoard`? locale, + ts: time, }); - itsYourTurnUsers.forEach((userId) => { - meta.players.byId[userId].itsYourTurn = true; - }); + const { board, playerboards = {}, secretboard = {} as GS["SB"] } = init; const users: UsersState = { byId: {} }; meta.players.allIds.forEach((userId) => { @@ -272,24 +304,27 @@ export class MatchTester< this.matchHasEnded = false; this.random = random; this.meta = meta; - this.time = 0; + this.time = time; this.delayedMoves = []; + // this.endOfTurnPlayerMoves = {}; this.users = users; this.matchSettings = matchSettings; this.matchPlayersSettings = matchPlayersSettings; this._sameBotCount = 0; - // Make the special initial move. - this._makeBoardMove(INIT_MOVE); - this._botTrainingLog = []; - this._stats = []; this._verbose = verbose; this._training = training; this._logBoardToTrainingLog = logBoardToTrainingLog; this._agents = {}; this._isPlaying = false; + this._lastTurnsBegin = new Set(); + + this.playerStats = {}; + this.matchStats = []; + // Make the special initial move. + this._makeBoardMove(INIT_MOVE); } /* @@ -357,7 +392,7 @@ export class MatchTester< * For the end of the match, as happens when all the players vote to end the match. */ abortMatch(): void { - this._endMatch({}); + this._endMatch(); this._makeBoardMove(MATCH_WAS_ABORTED); } @@ -366,10 +401,10 @@ export class MatchTester< * * This is also called if the game triggers the special `endMatch(..)` move. */ - _endMatch(endMatchOptions: EndMatchOptions): void { + _endMatch(): void { this.matchHasEnded = true; - metaMatchEnded(this.meta, endMatchOptions, this.game.playerScoreType); + // metaMatchEnded(this.meta); // It's no-one's turn anymore. this.meta.players.allIds.forEach((userId) => { @@ -388,8 +423,8 @@ export class MatchTester< } makeSpecialExecuteFuncs() { - const endMatch = (options?: EndMatchOptions) => { - this._endMatch(options || {}); + const endMatch = () => { + this._endMatch(); }; const reward = (payload: RewardPayload) => { @@ -399,35 +434,121 @@ export class MatchTester< } }; - const itsYourTurn = (payload: ItsYourTurnPayload): void => { - if (this.matchHasEnded) { - return; + const turnsbegin: Turns["begin"] = ( + userIds, + { expiresIn, boardMoveOnExpire, playerMoveOnExpire } = {}, + ) => { + userIds = parseTurnUserIds(userIds, { + allUserIds: this.meta.players.allIds, + }); + for (const userId of userIds) { + this._lastTurnsBegin.add(userId); + // Clear previous turn player moves for that player. + this.delayedMoves = this.delayedMoves.filter( + ({ userId: otherUserId }) => otherUserId !== userId, + ); + + this.meta.players.byId[userId].itsYourTurn = true; + if (playerMoveOnExpire) { + const { name, payload } = parseMove(playerMoveOnExpire); + if (expiresIn === undefined) { + throw new Error("expiresIn is required for playerMoveOnExpire"); + } + + this.delayedMoves.push({ + type: "playerMove", + name, + payload, + ts: this.time + expiresIn, + userId, + }); + + sortDelayedMoves(this.delayedMoves); + } + + // Note that it is one boardMove per user. + if (boardMoveOnExpire) { + const { name, payload } = parseMove(boardMoveOnExpire); + if (expiresIn === undefined) { + throw new Error("expiresIn is required for playerMoveOnExpire"); + } + this.delayedMoves.push({ + type: "boardMove", + name, + payload, + ts: this.time + expiresIn, + // We need the `userId` to stop it if the player makes a move. + userId, + }); + sortDelayedMoves(this.delayedMoves); + } } - metaItsYourTurn(this.meta, payload); + + return { + expiresAt: expiresIn === undefined ? undefined : this.time + expiresIn, + }; }; - const delayMove = ( - name: K, - ...payloadAndDelay: IfNever - ) => { + const turnsEnd: Turns["end"] = (userIds) => { + userIds = parseTurnUserIds(userIds, { + allUserIds: this.meta.players.allIds, + }); + for (const userId of userIds) { + this._lastTurnsBegin.delete(userId); + this.meta.players.byId[userId].itsYourTurn = false; + + // Clear previous turn player moves for that player. + this.delayedMoves = this.delayedMoves.filter( + ({ userId: otherUserId }) => otherUserId !== userId, + ); + } + }; + + const turns: Turns = { + end: turnsEnd, + begin: turnsbegin, + }; + + const delayMove: DelayMove = (name: string, ...payloadAndDelay: any[]) => { const [payload, delay] = payloadAndDelay.length === 1 ? [{}, payloadAndDelay[0]] : payloadAndDelay; - // const { name, payload } = move; - const dm = { name, payload, ts: this.time + delay }; + const ts = this.time + delay; + // In the match tester, we only note the delayed move. We'll execute them only if // we `fastForward`. - this.delayedMoves.push(dm); - return { ts: dm.ts }; + this.delayedMoves.push({ + type: "boardMove" as const, + name, + payload, + ts, + }); + + sortDelayedMoves(this.delayedMoves); + + return { ts }; + }; + + const logPlayerStat = (userId: UserId, key: string, value: number) => { + if (!this.game.playerStats?.byId[key]) { + throw new Error(`player stat "${key}" not defined`); + } + if (!this.playerStats[userId]) { + this.playerStats[userId] = []; + } + this.playerStats[userId].push({ key, value }); }; - const logStat = (key: string, value: number) => { - this._stats.push({ key, value }); + const logMatchStat = (key: string, value: number) => { + if (!this.game.matchStats?.byId[key]) { + throw new Error(`match stat "${key}" not defined`); + } + this.matchStats.push({ key, value }); }; - return { delayMove, itsYourTurn, endMatch, reward, logStat }; + return { delayMove, turns, endMatch, reward, logPlayerStat, logMatchStat }; } _makeBoardMove(moveName: string, payload: any = {}) { @@ -452,7 +573,9 @@ export class MatchTester< return; } - const { execute } = boardMoves[moveName]; + // Not sure why we need this `any`. It causes issues only when we `watch-compile` + // the code + const { execute } = boardMoves[moveName] as any; if (!execute) { console.warn(`board move "${moveName}" not defined`); @@ -479,6 +602,8 @@ export class MatchTester< console.warn(`board move "${moveName}" failed with error`); console.warn(e); } + + this.fastForward(0); } makeMove( @@ -486,7 +611,7 @@ export class MatchTester< moveName: K, ...rest: MakeMoveRest ) { - const [payload, { canFail = false }] = + const [payload, { canFail = false, isDelayed = false }] = rest.length === 0 ? [undefined, {}] : rest.length === 1 @@ -527,18 +652,23 @@ export class MatchTester< const { canDo, executeNow, execute } = game.playerMoves[moveName]; if ( - canDo !== undefined && + !isDelayed && + canDo && !canDo({ userId, board, playerboard, payload, ts: time }) ) { if (!canFail) { throw new Error(`can not do move "${moveName}"`); } + + console.warn(`can not do move "${moveName}"`); return; } const specialExecuteFuncs = this.makeSpecialExecuteFuncs(); - const { delayMove } = specialExecuteFuncs; + // const { turns } = specialExecuteFuncs; + + this._lastTurnsBegin.clear(); try { let retValue; @@ -548,7 +678,8 @@ export class MatchTester< board, playerboard, payload, - delayMove, + _: specialExecuteFuncs, + ...specialExecuteFuncs, }); } if (retValue !== false && execute) { @@ -562,6 +693,7 @@ export class MatchTester< payload, random, ts: time, + _: specialExecuteFuncs, ...specialExecuteFuncs, }); } @@ -572,6 +704,8 @@ export class MatchTester< console.warn(e); throw new Error("error in move"); } + + this.fastForward(0); } async start() { @@ -599,7 +733,12 @@ export class MatchTester< await this.makeNextBotMove(); } - async makeNextBotMove() { + async makeNextBotMove({ max = null }: { max?: number | null } = {}) { + if (!this._isPlaying) { + await this.start(); + // Return because `start` calls makeNextBotMove. + return; + } const { meta, game, autoMove, board, playerboards, secretboard, random } = this; // Check if we should do a bot move. @@ -608,7 +747,6 @@ export class MatchTester< userIndex < meta.players.allIds.length; ++userIndex ) { - //userId of meta.players.allIds) { const userId = meta.players.allIds[userIndex]; const { isBot, itsYourTurn } = meta.players.byId[userId]; @@ -672,17 +810,25 @@ export class MatchTester< } if (this._sameBotCount > 1000) { throw new Error( - "The same bot played too many times in a row. Did you forget to `yield itsYourTurn(...)`?", + "The same bot played too many times in a row. Did you forget to call `turns.end(...)`?", ); } // We only play one bot move per call. The function will be called again if it's // another bot's turn after. - return await this.makeMoveAndContinue( + this.makeMove( userId, name, ...((payload === undefined ? [] : [payload]) as any), ); + + // TODO Test this + if (max === null || max >= 2) { + await this.makeNextBotMove({ + max: max === null ? null : max - 1, + }); + } + return; } } // No bot played this time. @@ -706,30 +852,58 @@ export class MatchTester< * delta: time that passed in milli-seconds */ fastForward(delta: number): void { - this.time += delta; - // Execute the delayed updates that have happend during that time *in order*. If two - // have the same timestamp, execute the one that was queued first. - const delayedMoves = this.delayedMoves.filter((du) => du.ts <= this.time); - // Sort by time, but keep the order in case of equality. - delayedMoves - .map((u, i) => [u, i] as [DelayedBoardMove, number]) - .sort(([u1, i1], [u2, i2]) => Math.sign(u1.ts - u2.ts) || i1 - i2); - - for (const delayedMove of delayedMoves) { - const { name, payload } = delayedMove; - this._makeBoardMove(name, payload); + const { delayedMoves } = this; + + if (delayedMoves.length === 0) { + this.time += delta; + return; + } + + // Fast forward in increments, according to the delay moves that we have in store. + // Otherwise they might happen at the wrong timestamp. + const nextDelayedMove = this.delayedMoves[0]; + + const { ts } = nextDelayedMove; + const timeToNextDelayedMove = ts - this.time; + + const shouldExecute = timeToNextDelayedMove <= delta; + + if (shouldExecute) { + // Move the `time` to that delayed move's execution time. + this.time = ts; + + // Remove the move + this.delayedMoves.shift(); + + // Execute the delayed move. + { + const { type, name, payload, userId } = nextDelayedMove; + if (type === "playerMove") { + this.makeMove( + userId, + name, + ...([payload, { isDelayed: true }] as any), + ); + } else { + this._makeBoardMove(name, payload); + } + } + + // Keep fast-forwarding + const newDelta = delta - timeToNextDelayedMove; + if (newDelta > 0) { + this.fastForward(delta - timeToNextDelayedMove); + } + } else { + this.time += delta; } } get botTrainingLog() { return this._botTrainingLog; } +} - get stats() { - return this._stats; - } - - clearStats() { - this._stats = []; - } +function sortDelayedMoves(delayedMoves: DelayedMove[]): void { + delayedMoves.sort((a, b) => a.ts - b.ts); } diff --git a/packages/game/src/typing.ts b/packages/game/src/typing.ts deleted file mode 100644 index 7d1a781..0000000 --- a/packages/game/src/typing.ts +++ /dev/null @@ -1 +0,0 @@ -export type IfNever = [T] extends [never] ? TRUE : FALSE; diff --git a/packages/game/src/utils.ts b/packages/game/src/utils.ts new file mode 100644 index 0000000..81aa542 --- /dev/null +++ b/packages/game/src/utils.ts @@ -0,0 +1,34 @@ +import type { UserId } from "@lefun/core"; + +export const parseTurnUserIds = ( + userIds: UserId | UserId[] | "all", + { allUserIds }: { allUserIds: UserId[] }, +): UserId[] => { + if (userIds === "all") { + userIds = allUserIds; + } + if (!Array.isArray(userIds)) { + userIds = [userIds]; + } + return userIds; +}; + +/* + * 'move' | ['move', payload] => {name: 'move', payload} + */ +export function parseMove( + move: M, +): { + name: string; + payload: M extends string + ? undefined + : M extends [string, infer PP] + ? PP + : never; +} { + if (typeof move === "string") { + return { name: move, payload: undefined } as any; + } + const [name, payload] = move; + return { name, payload }; +} diff --git a/packages/ui-testing/package.json b/packages/ui-testing/package.json index d0155fd..9ad8a05 100644 --- a/packages/ui-testing/package.json +++ b/packages/ui-testing/package.json @@ -44,7 +44,7 @@ "react-dom": "18.3.1", "rollup": "^2.79.1", "rollup-plugin-typescript2": "^0.31.2", - "typescript": "^5.5.3" + "typescript": "^5.5.4" }, "peerDependencies": { "@lefun/ui": "workspace:*", diff --git a/packages/ui/package.json b/packages/ui/package.json index 76420a9..c94b534 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -59,7 +59,8 @@ "rollup": "^2.79.1", "rollup-plugin-typescript2": "^0.36.0", "tslib": "^2.6.3", - "typescript": "^5.5.3" + "typescript": "^5.5.4", + "vitest": "^2.0.5" }, "peerDependencies": { "@lefun/core": "workspace:*", diff --git a/packages/ui/src/index.test-d.ts b/packages/ui/src/index.test-d.ts new file mode 100644 index 0000000..e440845 --- /dev/null +++ b/packages/ui/src/index.test-d.ts @@ -0,0 +1,195 @@ +import { test } from "vitest"; + +import type { BoardMove, Game, GameState, PlayerMove } from "@lefun/game"; + +import { makeUseMakeMove, useMakeMove } from "./index"; + +test("makeMove - payload-typed", () => { + const move1: PlayerMove = { + executeNow: ({ payload }) => { + console.log(payload); + }, + }; + + const move2: PlayerMove = { + execute: () => { + // + }, + }; + + type GS = GameState<{ x: number }>; + + const game = { + initialBoards: () => ({ board: { x: 2 } }), + playerMoves: { move1, move2 }, + minPlayers: 1, + maxPlayers: 1, + } satisfies Game; + + type G = typeof game; + + const useMakeMove = makeUseMakeMove(); + const makeMove = useMakeMove(); + + // @ts-expect-error wrong move name + makeMove("patate"); + + makeMove("move1"); + + // @ts-expect-error missing payload + makeMove("move2"); + + makeMove("move2", { x: 123 }); + // @ts-expect-error superfluous payload + makeMove("move1", { x: 123 }); + + // @ts-expect-error wrong payload type + makeMove("move2", { x: "123" }); + + // @ts-expect-error wrong payload key + makeMove("move2", { y: 123 }); +}); + +test("makeMove - not-payload-typed", () => { + const move1: PlayerMove = { + executeNow: ({ payload }) => { + console.log(payload); + }, + }; + + const move2: PlayerMove = { + execute: () => { + // + }, + }; + + type GS = GameState<{ x: number }>; + + const game = { + initialBoards: () => ({ board: { x: 2 } }), + playerMoves: { move1, move2 }, + minPlayers: 1, + maxPlayers: 1, + } satisfies Game; + + type G = typeof game; + + { + const useMakeMove = makeUseMakeMove(); + const makeMove = useMakeMove(); + + // @ts-expect-error wrong move name + makeMove("patate"); + + // They all work because we didn't specify the payload type. + makeMove("move1"); + makeMove("move2"); + makeMove("move2", { x: 123 }); + makeMove("move1", { x: 123 }); + makeMove("move2", { x: "123" }); + makeMove("move2", { y: 123 }); + } + + // No `makeUseMakeMove` but with the . + { + const makeMove = useMakeMove(); + + // @ts-expect-error wrong move name + makeMove("patate"); + + makeMove("move1"); + makeMove("move2"); + makeMove("move2", { x: 123 }); + makeMove("move1", { x: 123 }); + makeMove("move2", { x: "123" }); + makeMove("move2", { y: 123 }); + } + + // No `G`. + { + const makeMove = useMakeMove(); + + makeMove("patate"); + makeMove("move1"); + makeMove("move2"); + makeMove("move2", { x: 123 }); + makeMove("move1", { x: 123 }); + makeMove("move2", { x: "123" }); + makeMove("move2", { y: 123 }); + } +}); + +test("makeMove - player-moves-typed", () => { + type Move2Payload = { + x: number; + }; + + type PMT = { + move1: null; + move2: Move2Payload; + move3: Move2Payload; + }; + + type BMT = { + boardMove: null; + }; + + const move1: PlayerMove = { + execute() {}, + executeNow({ payload }) { + console.log(payload); + }, + }; + + const move2: PlayerMove = { + execute({ payload }) { + console.log(payload); + }, + executeNow({ payload }) { + console.log(payload); + }, + }; + + const move3: PlayerMove = { + executeNow({ payload }) { + console.log(payload); + }, + }; + + const boardMove: BoardMove = { + execute() {}, + }; + + type GS = GameState<{ x: number }>; + + const game = { + initialBoards: () => ({ board: { x: 2 } }), + playerMoves: { move1, move2, move3 }, + boardMoves: { boardMove }, + minPlayers: 1, + maxPlayers: 1, + } satisfies Game; + + type G = typeof game; + + const useMakeMove = makeUseMakeMove(); + const makeMove = useMakeMove(); + + // @ts-expect-error wrong move name + makeMove("patate"); + + makeMove("move1"); + + // @ts-expect-error missing payload + makeMove("move2"); + + makeMove("move2", { x: 123 }); + // @ts-expect-error superfluous payload + makeMove("move1", { x: 123 }); + + // @ts-expect-error wrong payload type + makeMove("move2", { x: "123" }); + + // @ts-expect-error wrong payload key + makeMove("move2", { y: 123 }); +}); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index d095f38..7273284 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -2,7 +2,7 @@ import { createContext, useContext, useMemo } from "react"; import { StoreApi, useStore as _useStore } from "zustand"; import { useShallow } from "zustand/react/shallow"; -import type { MatchState as _MatchState, UserId } from "@lefun/core"; +import type { IfAnyNull, MatchState as _MatchState, UserId } from "@lefun/core"; import type { Game, GameStateBase, GetPayload } from "@lefun/game"; // In the selectors, assume that the boards are defined. We will add a check in the @@ -27,38 +27,37 @@ export type Store = StoreApi< export const storeContext = createContext(null); -type IfNever = [T] extends [never] ? TRUE : FALSE; - -type MakeMove> = < - K extends keyof G["playerMoves"] & string, ->( - moveName: K, - ...payload: IfNever, [], [GetPayload]> -) => void; - -type MakeMoveFull> = < - K extends keyof G["playerMoves"] & string, ->( +type MakeMove = ( moveName: K, - ...payload: IfNever< + ...payload: IfAnyNull< GetPayload, - [GetPayload | undefined], + // any + [] | [any], + // null + [], + // other [GetPayload] > ) => void; -let _makeMove: ((store: Store) => MakeMoveFull) | null = null; +type MakeMoveFull = ( + moveName: K, + payload: GetPayload, +) => void; + +let _makeMove: ((store: Store) => MakeMoveFull) | null = + null; export function setMakeMove( - makeMove: (store: Store) => MakeMoveFull>, + makeMove: (store: Store) => MakeMoveFull, ) { _makeMove = makeMove; } -type GetGameStatesFromGame> = - G extends Game ? GS : never; +type GetGameStatesFromGame = + G extends Game ? GS : never; -export function useMakeMove>(): MakeMove { +export function useMakeMove(): MakeMove { const makeMove = _makeMove; if (!makeMove) { @@ -78,20 +77,25 @@ export function useMakeMove>(): MakeMove { // `_makeMove` returns a new function every time it's called, but we don't want to // re-render. return useMemo(() => { - const makeMovefull = makeMove(store); + const makeMovefull = makeMove(store); function newMakeMove( moveName: K, - ...payload: IfNever, [], [GetPayload]> + ...payload: IfAnyNull< + GetPayload, + [] | [any], + [], + [GetPayload] + > ) { - return makeMovefull(moveName, payload[0] || {}); + return makeMovefull(moveName, payload[0] || null); } return newMakeMove; }, [store, makeMove]); } -export function makeUseMakeMove>() { +export function makeUseMakeMove() { return useMakeMove; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8eb430..3a0c31b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,36 +16,36 @@ importers: specifier: ^3.3.3 version: 3.3.3 - game-template/game: + games/game1-v2.3.0/game: devDependencies: '@lefun/core': specifier: workspace:* - version: link:../../packages/core + version: link:../../../packages/core '@lefun/game': specifier: workspace:* - version: link:../../packages/game + version: link:../../../packages/game rollup: - specifier: ^4.18.1 - version: 4.18.1 + specifier: ^4.20.0 + version: 4.20.0 rollup-plugin-typescript2: specifier: ^0.36.0 - version: 0.36.0(rollup@4.18.1)(typescript@5.5.3) + version: 0.36.0(rollup@4.20.0)(typescript@5.5.4) tslib: specifier: ^2.6.3 version: 2.6.3 typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.10) + specifier: ^2.0.5 + version: 2.0.5(@types/node@20.14.10) - game-template/ui: + games/game1-v2.3.0/ui: dependencies: classnames: specifier: ^2.5.1 version: 2.5.1 - roll-game: + game1-v2.3.0-game: specifier: workspace:* version: link:../game devDependencies: @@ -54,28 +54,28 @@ importers: version: 7.24.7(@babel/core@7.24.9) '@lefun/core': specifier: workspace:* - version: link:../../packages/core + version: link:../../../packages/core '@lefun/dev-server': specifier: workspace:* - version: link:../../packages/dev-server + version: link:../../../packages/dev-server '@lefun/ui': specifier: workspace:* - version: link:../../packages/ui + version: link:../../../packages/ui '@lingui/cli': specifier: ^4.11.2 - version: 4.11.2(typescript@5.5.3) + version: 4.11.2(typescript@5.5.4) '@lingui/conf': specifier: ^4.11.2 - version: 4.11.2(typescript@5.5.3) + version: 4.11.2(typescript@5.5.4) '@lingui/macro': specifier: ^4.11.2 - version: 4.11.2(@lingui/react@4.11.2(react@18.3.1))(babel-plugin-macros@3.1.0)(typescript@5.5.3) + version: 4.11.2(@lingui/react@4.11.2(react@18.3.1))(babel-plugin-macros@3.1.0)(typescript@5.5.4) '@lingui/react': specifier: ^4.11.2 version: 4.11.2(react@18.3.1) '@lingui/vite-plugin': specifier: ^4.11.2 - version: 4.11.2(typescript@5.5.3)(vite@5.3.4(@types/node@20.14.10)) + version: 4.11.2(typescript@5.5.4)(vite@5.3.4(@types/node@20.14.10)) '@rollup/plugin-babel': specifier: ^6.0.4 version: 6.0.4(@babel/core@7.24.9)(@types/babel__core@7.20.5)(rollup@4.18.1) @@ -108,13 +108,13 @@ importers: version: 3.5.0 rollup-plugin-postcss: specifier: ^4.0.2 - version: 4.0.2(postcss@8.4.39) + version: 4.0.2(postcss@8.4.40) rollup-plugin-typescript2: specifier: ^0.36.0 - version: 0.36.0(rollup@4.18.1)(typescript@5.5.3) + version: 0.36.0(rollup@4.18.1)(typescript@5.5.4) typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 vite: specifier: ^5.3.4 version: 5.3.4(@types/node@20.14.10) @@ -129,10 +129,10 @@ importers: version: 13.3.0(rollup@2.79.1) '@typescript-eslint/eslint-plugin': specifier: ^7.16.1 - version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/parser': specifier: ^7.16.1 - version: 7.16.1(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(eslint@8.57.0)(typescript@5.5.4) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -147,16 +147,16 @@ importers: version: 2.79.1 rollup-plugin-typescript2: specifier: ^0.31.2 - version: 0.31.2(rollup@2.79.1)(typescript@5.5.3) + version: 0.31.2(rollup@2.79.1)(typescript@5.5.4) tslib: specifier: ^2.6.3 version: 2.6.3 typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.10) + specifier: ^2.0.5 + version: 2.0.5(@types/node@20.14.10) packages/dev-server: dependencies: @@ -196,7 +196,7 @@ importers: version: 15.2.3(rollup@4.18.1) '@rollup/plugin-typescript': specifier: ^11.1.6 - version: 11.1.6(rollup@4.18.1)(tslib@2.6.3)(typescript@5.5.3) + version: 11.1.6(rollup@4.18.1)(tslib@2.6.3)(typescript@5.5.4) '@types/react': specifier: 18.3.3 version: 18.3.3 @@ -205,10 +205,10 @@ importers: version: 18.3.0 '@typescript-eslint/eslint-plugin': specifier: ^7.16.1 - version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/parser': specifier: ^7.16.1 - version: 7.16.1(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(eslint@8.57.0)(typescript@5.5.4) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.39) @@ -246,11 +246,11 @@ importers: specifier: ^3.4.6 version: 3.4.6 typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.10) + specifier: ^2.0.5 + version: 2.0.5(@types/node@20.14.10) packages/game: dependencies: @@ -272,10 +272,10 @@ importers: version: 4.17.12 '@typescript-eslint/eslint-plugin': specifier: ^7.16.1 - version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/parser': specifier: ^7.16.1 - version: 7.16.1(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(eslint@8.57.0)(typescript@5.5.4) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -290,16 +290,16 @@ importers: version: 2.79.1 rollup-plugin-typescript2: specifier: ^0.31.2 - version: 0.31.2(rollup@2.79.1)(typescript@5.5.3) + version: 0.31.2(rollup@2.79.1)(typescript@5.5.4) tslib: specifier: ^2.6.3 version: 2.6.3 typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.10) + specifier: ^2.0.5 + version: 2.0.5(@types/node@20.14.10) packages/ui: dependencies: @@ -315,10 +315,10 @@ importers: version: link:../game '@lingui/cli': specifier: ^4.11.2 - version: 4.11.2(typescript@5.5.3) + version: 4.11.2(typescript@5.5.4) '@lingui/macro': specifier: ^4.11.2 - version: 4.11.2(@lingui/react@4.11.2(react@18.3.1))(babel-plugin-macros@3.1.0)(typescript@5.5.3) + version: 4.11.2(@lingui/react@4.11.2(react@18.3.1))(babel-plugin-macros@3.1.0)(typescript@5.5.4) '@rollup/plugin-commonjs': specifier: ^21.1.0 version: 21.1.0(rollup@2.79.1) @@ -336,10 +336,10 @@ importers: version: 18.3.0 '@typescript-eslint/eslint-plugin': specifier: ^7.16.1 - version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/parser': specifier: ^7.16.1 - version: 7.16.1(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(eslint@8.57.0)(typescript@5.5.4) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -363,13 +363,16 @@ importers: version: 2.79.1 rollup-plugin-typescript2: specifier: ^0.36.0 - version: 0.36.0(rollup@2.79.1)(typescript@5.5.3) + version: 0.36.0(rollup@2.79.1)(typescript@5.5.4) tslib: specifier: ^2.6.3 version: 2.6.3 typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 + vitest: + specifier: ^2.0.5 + version: 2.0.5(@types/node@20.14.10) packages/ui-testing: dependencies: @@ -400,10 +403,10 @@ importers: version: 18.3.3 '@typescript-eslint/eslint-plugin': specifier: ^7.16.1 - version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/parser': specifier: ^7.16.1 - version: 7.16.1(eslint@8.57.0)(typescript@5.5.3) + version: 7.16.1(eslint@8.57.0)(typescript@5.5.4) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -427,10 +430,10 @@ importers: version: 2.79.1 rollup-plugin-typescript2: specifier: ^0.31.2 - version: 0.31.2(rollup@2.79.1)(typescript@5.5.3) + version: 0.31.2(rollup@2.79.1)(typescript@5.5.4) typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 packages: @@ -577,6 +580,10 @@ packages: resolution: {integrity: sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.25.0': + resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.24.7': resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} engines: {node: '>=6.9.0'} @@ -1273,81 +1280,161 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.20.0': + resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.18.1': resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==} cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.20.0': + resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.18.1': resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.20.0': + resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.18.1': resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==} cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.20.0': + resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==} + cpu: [x64] + os: [darwin] + '@rollup/rollup-linux-arm-gnueabihf@4.18.1': resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': + resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.18.1': resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.20.0': + resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.18.1': resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.20.0': + resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-musl@4.18.1': resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-musl@4.20.0': + resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.18.1': resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==} cpu: [ppc64] os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': + resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==} + cpu: [ppc64] + os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.18.1': resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==} cpu: [riscv64] os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.20.0': + resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.18.1': resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==} cpu: [s390x] os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.20.0': + resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==} + cpu: [s390x] + os: [linux] + '@rollup/rollup-linux-x64-gnu@4.18.1': resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-gnu@4.20.0': + resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-musl@4.18.1': resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-musl@4.20.0': + resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==} + cpu: [x64] + os: [linux] + '@rollup/rollup-win32-arm64-msvc@4.18.1': resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.20.0': + resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.18.1': resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.20.0': + resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==} + cpu: [ia32] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.18.1': resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.20.0': + resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==} + cpu: [x64] + os: [win32] + '@sigstore/bundle@2.3.2': resolution: {integrity: sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==} engines: {node: ^16.14.0 || >=18.0.0} @@ -1558,20 +1645,23 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitest/expect@1.6.0': - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + '@vitest/expect@2.0.5': + resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + + '@vitest/pretty-format@2.0.5': + resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} - '@vitest/runner@1.6.0': - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + '@vitest/runner@2.0.5': + resolution: {integrity: sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==} - '@vitest/snapshot@1.6.0': - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + '@vitest/snapshot@2.0.5': + resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==} - '@vitest/spy@1.6.0': - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + '@vitest/spy@2.0.5': + resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} - '@vitest/utils@1.6.0': - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@vitest/utils@2.0.5': + resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} '@yarn-tool/resolve-package@1.0.47': resolution: {integrity: sha512-Zaw58gQxjQceJqhqybJi1oUDaORT8i2GTgwICPs8v/X/Pkx35FXQba69ldHVg5pQZ6YLKpROXgyHvBaCJOFXiA==} @@ -1600,10 +1690,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.3: - resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} - engines: {node: '>=0.4.0'} - acorn@8.12.1: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} @@ -1696,8 +1782,9 @@ packages: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} engines: {node: '>=8'} - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} @@ -1805,9 +1892,9 @@ packages: caniuse-lite@1.0.30001642: resolution: {integrity: sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==} - chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -1824,8 +1911,9 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} chokidar@3.5.1: resolution: {integrity: sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==} @@ -1959,9 +2047,6 @@ packages: concat-with-sourcemaps@1.1.0: resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} - confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} - console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} @@ -2085,6 +2170,15 @@ packages: supports-color: optional: true + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -2101,8 +2195,8 @@ packages: babel-plugin-macros: optional: true - deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} deep-is@0.1.4: @@ -2856,9 +2950,6 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.0: - resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -2984,10 +3075,6 @@ packages: resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} engines: {node: '>= 12.13.0'} - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} @@ -3039,8 +3126,8 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3062,6 +3149,9 @@ packages: magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -3202,9 +3292,6 @@ packages: engines: {node: '>=10'} hasBin: true - mlly@1.7.1: - resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} - modify-values@1.0.1: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} @@ -3404,10 +3491,6 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - p-locate@2.0.0: resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} engines: {node: '>=4'} @@ -3538,8 +3621,9 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -3576,9 +3660,6 @@ packages: resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} engines: {node: '>=10'} - pkg-types@1.1.3: - resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} - pkg-up@3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} @@ -3863,6 +3944,10 @@ packages: resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.40: + resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} + engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -4095,6 +4180,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.20.0: + resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -4293,9 +4383,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@2.1.0: - resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} - strong-log-transformer@2.1.0: resolution: {integrity: sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==} engines: {node: '>=4'} @@ -4372,15 +4459,19 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tinybench@2.8.0: - resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + tinypool@1.0.0: + resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + tinyspy@3.0.0: + resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} engines: {node: '>=14.0.0'} tmp@0.0.33: @@ -4440,10 +4531,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - type-fest@0.18.1: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} @@ -4476,8 +4563,10 @@ packages: engines: {node: '>=14.17'} hasBin: true - ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true uglify-js@3.18.0: resolution: {integrity: sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==} @@ -4548,8 +4637,8 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - vite-node@1.6.0: - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + vite-node@2.0.5: + resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -4581,15 +4670,43 @@ packages: terser: optional: true - vitest@1.6.0: - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + vite@5.3.5: + resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.0.5: + resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 + '@vitest/browser': 2.0.5 + '@vitest/ui': 2.0.5 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -4722,10 +4839,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.1.1: - resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} - engines: {node: '>=12.20'} - zustand@4.5.4: resolution: {integrity: sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==} engines: {node: '>=12.7.0'} @@ -4922,6 +5035,10 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.25.0': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.24.7': dependencies: '@babel/code-frame': 7.24.7 @@ -5246,7 +5363,7 @@ snapshots: '@lingui/babel-plugin-extract-messages@4.11.2': {} - '@lingui/cli@4.11.2(typescript@5.5.3)': + '@lingui/cli@4.11.2(typescript@5.5.4)': dependencies: '@babel/core': 7.24.9 '@babel/generator': 7.24.10 @@ -5254,9 +5371,9 @@ snapshots: '@babel/runtime': 7.24.8 '@babel/types': 7.24.9 '@lingui/babel-plugin-extract-messages': 4.11.2 - '@lingui/conf': 4.11.2(typescript@5.5.3) + '@lingui/conf': 4.11.2(typescript@5.5.4) '@lingui/core': 4.11.2 - '@lingui/format-po': 4.11.2(typescript@5.5.3) + '@lingui/format-po': 4.11.2(typescript@5.5.4) '@lingui/message-utils': 4.11.2 babel-plugin-macros: 3.1.0 chalk: 4.1.2 @@ -5281,11 +5398,11 @@ snapshots: - supports-color - typescript - '@lingui/conf@4.11.2(typescript@5.5.3)': + '@lingui/conf@4.11.2(typescript@5.5.4)': dependencies: '@babel/runtime': 7.24.8 chalk: 4.1.2 - cosmiconfig: 8.3.6(typescript@5.5.3) + cosmiconfig: 8.3.6(typescript@5.5.4) jest-validate: 29.7.0 jiti: 1.21.6 lodash.get: 4.4.2 @@ -5298,20 +5415,20 @@ snapshots: '@lingui/message-utils': 4.11.2 unraw: 3.0.0 - '@lingui/format-po@4.11.2(typescript@5.5.3)': + '@lingui/format-po@4.11.2(typescript@5.5.4)': dependencies: - '@lingui/conf': 4.11.2(typescript@5.5.3) + '@lingui/conf': 4.11.2(typescript@5.5.4) '@lingui/message-utils': 4.11.2 date-fns: 3.6.0 pofile: 1.1.4 transitivePeerDependencies: - typescript - '@lingui/macro@4.11.2(@lingui/react@4.11.2(react@18.3.1))(babel-plugin-macros@3.1.0)(typescript@5.5.3)': + '@lingui/macro@4.11.2(@lingui/react@4.11.2(react@18.3.1))(babel-plugin-macros@3.1.0)(typescript@5.5.4)': dependencies: '@babel/runtime': 7.24.8 '@babel/types': 7.24.9 - '@lingui/conf': 4.11.2(typescript@5.5.3) + '@lingui/conf': 4.11.2(typescript@5.5.4) '@lingui/core': 4.11.2 '@lingui/message-utils': 4.11.2 '@lingui/react': 4.11.2(react@18.3.1) @@ -5330,10 +5447,10 @@ snapshots: '@lingui/core': 4.11.2 react: 18.3.1 - '@lingui/vite-plugin@4.11.2(typescript@5.5.3)(vite@5.3.4(@types/node@20.14.10))': + '@lingui/vite-plugin@4.11.2(typescript@5.5.4)(vite@5.3.4(@types/node@20.14.10))': dependencies: - '@lingui/cli': 4.11.2(typescript@5.5.3) - '@lingui/conf': 4.11.2(typescript@5.5.3) + '@lingui/cli': 4.11.2(typescript@5.5.4) + '@lingui/conf': 4.11.2(typescript@5.5.4) vite: 5.3.4(@types/node@20.14.10) transitivePeerDependencies: - supports-color @@ -5716,11 +5833,11 @@ snapshots: optionalDependencies: rollup: 4.18.1 - '@rollup/plugin-typescript@11.1.6(rollup@4.18.1)(tslib@2.6.3)(typescript@5.5.3)': + '@rollup/plugin-typescript@11.1.6(rollup@4.18.1)(tslib@2.6.3)(typescript@5.5.4)': dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.18.1) resolve: 1.22.8 - typescript: 5.5.3 + typescript: 5.5.4 optionalDependencies: rollup: 4.18.1 tslib: 2.6.3 @@ -5756,51 +5873,99 @@ snapshots: '@rollup/rollup-android-arm-eabi@4.18.1': optional: true + '@rollup/rollup-android-arm-eabi@4.20.0': + optional: true + '@rollup/rollup-android-arm64@4.18.1': optional: true + '@rollup/rollup-android-arm64@4.20.0': + optional: true + '@rollup/rollup-darwin-arm64@4.18.1': optional: true + '@rollup/rollup-darwin-arm64@4.20.0': + optional: true + '@rollup/rollup-darwin-x64@4.18.1': optional: true + '@rollup/rollup-darwin-x64@4.20.0': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.18.1': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.18.1': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.20.0': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.18.1': optional: true + '@rollup/rollup-linux-arm64-gnu@4.20.0': + optional: true + '@rollup/rollup-linux-arm64-musl@4.18.1': optional: true + '@rollup/rollup-linux-arm64-musl@4.20.0': + optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.18.1': optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.18.1': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.20.0': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.18.1': optional: true + '@rollup/rollup-linux-s390x-gnu@4.20.0': + optional: true + '@rollup/rollup-linux-x64-gnu@4.18.1': optional: true + '@rollup/rollup-linux-x64-gnu@4.20.0': + optional: true + '@rollup/rollup-linux-x64-musl@4.18.1': optional: true + '@rollup/rollup-linux-x64-musl@4.20.0': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.18.1': optional: true + '@rollup/rollup-win32-arm64-msvc@4.20.0': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.18.1': optional: true + '@rollup/rollup-win32-ia32-msvc@4.20.0': + optional: true + '@rollup/rollup-win32-x64-msvc@4.18.1': optional: true + '@rollup/rollup-win32-x64-msvc@4.20.0': + optional: true + '@sigstore/bundle@2.3.2': dependencies: '@sigstore/protobuf-specs': 0.3.2 @@ -5840,7 +6005,7 @@ snapshots: '@testing-library/dom@10.1.0': dependencies: '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.24.8 + '@babel/runtime': 7.25.0 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -5956,34 +6121,34 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.16.1(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': 7.16.1(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/scope-manager': 7.16.1 - '@typescript-eslint/type-utils': 7.16.1(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/type-utils': 7.16.1(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/visitor-keys': 7.16.1 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.5.3) + ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: - typescript: 5.5.3 + typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@typescript-eslint/scope-manager': 7.16.1 '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) + '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.4) '@typescript-eslint/visitor-keys': 7.16.1 debug: 4.3.5 eslint: 8.57.0 optionalDependencies: - typescript: 5.5.3 + typescript: 5.5.4 transitivePeerDependencies: - supports-color @@ -5992,21 +6157,21 @@ snapshots: '@typescript-eslint/types': 7.16.1 '@typescript-eslint/visitor-keys': 7.16.1 - '@typescript-eslint/type-utils@7.16.1(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/type-utils@7.16.1(eslint@8.57.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.4) + '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.5.4) debug: 4.3.5 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.5.3) + ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: - typescript: 5.5.3 + typescript: 5.5.4 transitivePeerDependencies: - supports-color '@typescript-eslint/types@7.16.1': {} - '@typescript-eslint/typescript-estree@7.16.1(typescript@5.5.3)': + '@typescript-eslint/typescript-estree@7.16.1(typescript@5.5.4)': dependencies: '@typescript-eslint/types': 7.16.1 '@typescript-eslint/visitor-keys': 7.16.1 @@ -6015,18 +6180,18 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.2 - ts-api-utils: 1.3.0(typescript@5.5.3) + ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: - typescript: 5.5.3 + typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.16.1(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/utils@7.16.1(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@typescript-eslint/scope-manager': 7.16.1 '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) + '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.4) eslint: 8.57.0 transitivePeerDependencies: - supports-color @@ -6050,34 +6215,38 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/expect@1.6.0': + '@vitest/expect@2.0.5': + dependencies: + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.0.5': dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - chai: 4.4.1 + tinyrainbow: 1.2.0 - '@vitest/runner@1.6.0': + '@vitest/runner@2.0.5': dependencies: - '@vitest/utils': 1.6.0 - p-limit: 5.0.0 + '@vitest/utils': 2.0.5 pathe: 1.1.2 - '@vitest/snapshot@1.6.0': + '@vitest/snapshot@2.0.5': dependencies: - magic-string: 0.30.10 + '@vitest/pretty-format': 2.0.5 + magic-string: 0.30.11 pathe: 1.1.2 - pretty-format: 29.7.0 - '@vitest/spy@1.6.0': + '@vitest/spy@2.0.5': dependencies: - tinyspy: 2.2.1 + tinyspy: 3.0.0 - '@vitest/utils@1.6.0': + '@vitest/utils@2.0.5': dependencies: - diff-sequences: 29.6.3 + '@vitest/pretty-format': 2.0.5 estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + loupe: 3.1.1 + tinyrainbow: 1.2.0 '@yarn-tool/resolve-package@1.0.47': dependencies: @@ -6107,10 +6276,6 @@ snapshots: dependencies: acorn: 8.12.1 - acorn-walk@8.3.3: - dependencies: - acorn: 8.12.1 - acorn@8.12.1: {} add-stream@1.0.0: {} @@ -6186,7 +6351,7 @@ snapshots: arrify@2.0.1: {} - assertion-error@1.1.0: {} + assertion-error@2.0.1: {} async@3.2.5: {} @@ -6310,15 +6475,13 @@ snapshots: caniuse-lite@1.0.30001642: {} - chai@4.4.1: + chai@5.1.1: dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 chalk@2.4.2: dependencies: @@ -6338,9 +6501,7 @@ snapshots: chardet@0.7.0: {} - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 + check-error@2.1.1: {} chokidar@3.5.1: dependencies: @@ -6469,8 +6630,6 @@ snapshots: dependencies: source-map: 0.6.1 - confbox@0.1.7: {} - console-control-strings@1.1.0: {} conventional-changelog-angular@7.0.0: @@ -6546,6 +6705,15 @@ snapshots: optionalDependencies: typescript: 5.5.3 + cosmiconfig@8.3.6(typescript@5.5.4): + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.5.4 + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -6556,6 +6724,10 @@ snapshots: dependencies: postcss: 8.4.39 + css-declaration-sorter@6.4.1(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + css-select@4.3.0: dependencies: boolbase: 1.0.0 @@ -6606,10 +6778,47 @@ snapshots: postcss-svgo: 5.1.0(postcss@8.4.39) postcss-unique-selectors: 5.1.1(postcss@8.4.39) + cssnano-preset-default@5.2.14(postcss@8.4.40): + dependencies: + css-declaration-sorter: 6.4.1(postcss@8.4.40) + cssnano-utils: 3.1.0(postcss@8.4.40) + postcss: 8.4.40 + postcss-calc: 8.2.4(postcss@8.4.40) + postcss-colormin: 5.3.1(postcss@8.4.40) + postcss-convert-values: 5.1.3(postcss@8.4.40) + postcss-discard-comments: 5.1.2(postcss@8.4.40) + postcss-discard-duplicates: 5.1.0(postcss@8.4.40) + postcss-discard-empty: 5.1.1(postcss@8.4.40) + postcss-discard-overridden: 5.1.0(postcss@8.4.40) + postcss-merge-longhand: 5.1.7(postcss@8.4.40) + postcss-merge-rules: 5.1.4(postcss@8.4.40) + postcss-minify-font-values: 5.1.0(postcss@8.4.40) + postcss-minify-gradients: 5.1.1(postcss@8.4.40) + postcss-minify-params: 5.1.4(postcss@8.4.40) + postcss-minify-selectors: 5.2.1(postcss@8.4.40) + postcss-normalize-charset: 5.1.0(postcss@8.4.40) + postcss-normalize-display-values: 5.1.0(postcss@8.4.40) + postcss-normalize-positions: 5.1.1(postcss@8.4.40) + postcss-normalize-repeat-style: 5.1.1(postcss@8.4.40) + postcss-normalize-string: 5.1.0(postcss@8.4.40) + postcss-normalize-timing-functions: 5.1.0(postcss@8.4.40) + postcss-normalize-unicode: 5.1.1(postcss@8.4.40) + postcss-normalize-url: 5.1.0(postcss@8.4.40) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.40) + postcss-ordered-values: 5.1.3(postcss@8.4.40) + postcss-reduce-initial: 5.1.2(postcss@8.4.40) + postcss-reduce-transforms: 5.1.0(postcss@8.4.40) + postcss-svgo: 5.1.0(postcss@8.4.40) + postcss-unique-selectors: 5.1.1(postcss@8.4.40) + cssnano-utils@3.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 + cssnano-utils@3.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + cssnano@5.1.15(postcss@8.4.39): dependencies: cssnano-preset-default: 5.2.14(postcss@8.4.39) @@ -6617,6 +6826,13 @@ snapshots: postcss: 8.4.39 yaml: 1.10.2 + cssnano@5.1.15(postcss@8.4.40): + dependencies: + cssnano-preset-default: 5.2.14(postcss@8.4.40) + lilconfig: 2.1.0 + postcss: 8.4.40 + yaml: 1.10.2 + csso@4.2.0: dependencies: css-tree: 1.1.3 @@ -6633,6 +6849,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.6: + dependencies: + ms: 2.1.2 + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 @@ -6644,9 +6864,7 @@ snapshots: optionalDependencies: babel-plugin-macros: 3.1.0 - deep-eql@4.1.4: - dependencies: - type-detect: 4.0.8 + deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -7260,6 +7478,10 @@ snapshots: dependencies: postcss: 8.4.39 + icss-utils@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + ieee754@1.2.1: {} ignore-walk@6.0.5: @@ -7471,8 +7693,6 @@ snapshots: js-tokens@4.0.0: {} - js-tokens@9.0.0: {} - js-yaml@3.14.1: dependencies: argparse: 1.0.10 @@ -7677,11 +7897,6 @@ snapshots: loader-utils@3.3.1: {} - local-pkg@0.5.0: - dependencies: - mlly: 1.7.1 - pkg-types: 1.1.3 - locate-path@2.0.0: dependencies: p-locate: 2.0.0 @@ -7727,7 +7942,7 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@2.3.7: + loupe@3.1.1: dependencies: get-func-name: 2.0.2 @@ -7751,6 +7966,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -7900,13 +8119,6 @@ snapshots: mkdirp@1.0.4: {} - mlly@1.7.1: - dependencies: - acorn: 8.12.1 - pathe: 1.1.2 - pkg-types: 1.1.3 - ufo: 1.5.4 - modify-values@1.0.1: {} moo@0.5.2: {} @@ -8171,10 +8383,6 @@ snapshots: dependencies: yocto-queue: 0.1.0 - p-limit@5.0.0: - dependencies: - yocto-queue: 1.1.1 - p-locate@2.0.0: dependencies: p-limit: 1.3.0 @@ -8308,7 +8516,7 @@ snapshots: pathe@1.1.2: {} - pathval@1.1.1: {} + pathval@2.0.0: {} picocolors@1.0.1: {} @@ -8332,12 +8540,6 @@ snapshots: dependencies: find-up: 5.0.0 - pkg-types@1.1.3: - dependencies: - confbox: 0.1.7 - mlly: 1.7.1 - pathe: 1.1.2 - pkg-up@3.1.0: dependencies: find-up: 3.0.0 @@ -8350,6 +8552,12 @@ snapshots: postcss-selector-parser: 6.1.1 postcss-value-parser: 4.2.0 + postcss-calc@8.2.4(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-selector-parser: 6.1.1 + postcss-value-parser: 4.2.0 + postcss-cli@11.0.0(jiti@1.21.6)(postcss@8.4.39): dependencies: chokidar: 3.6.0 @@ -8377,28 +8585,58 @@ snapshots: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-colormin@5.3.1(postcss@8.4.40): + dependencies: + browserslist: 4.23.2 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-convert-values@5.1.3(postcss@8.4.39): dependencies: browserslist: 4.23.2 postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-convert-values@5.1.3(postcss@8.4.40): + dependencies: + browserslist: 4.23.2 + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-discard-comments@5.1.2(postcss@8.4.39): dependencies: postcss: 8.4.39 + postcss-discard-comments@5.1.2(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-discard-duplicates@5.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 + postcss-discard-duplicates@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-discard-empty@5.1.1(postcss@8.4.39): dependencies: postcss: 8.4.39 + postcss-discard-empty@5.1.1(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-discard-overridden@5.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 + postcss-discard-overridden@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-import@15.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 @@ -8418,6 +8656,13 @@ snapshots: optionalDependencies: postcss: 8.4.39 + postcss-load-config@3.1.4(postcss@8.4.40): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + optionalDependencies: + postcss: 8.4.40 + postcss-load-config@4.0.2(postcss@8.4.39): dependencies: lilconfig: 3.1.2 @@ -8439,6 +8684,12 @@ snapshots: postcss-value-parser: 4.2.0 stylehacks: 5.1.1(postcss@8.4.39) + postcss-merge-longhand@5.1.7(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + stylehacks: 5.1.1(postcss@8.4.40) + postcss-merge-rules@5.1.4(postcss@8.4.39): dependencies: browserslist: 4.23.2 @@ -8447,11 +8698,24 @@ snapshots: postcss: 8.4.39 postcss-selector-parser: 6.1.1 + postcss-merge-rules@5.1.4(postcss@8.4.40): + dependencies: + browserslist: 4.23.2 + caniuse-api: 3.0.0 + cssnano-utils: 3.1.0(postcss@8.4.40) + postcss: 8.4.40 + postcss-selector-parser: 6.1.1 + postcss-minify-font-values@5.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-minify-font-values@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-minify-gradients@5.1.1(postcss@8.4.39): dependencies: colord: 2.9.3 @@ -8459,6 +8723,13 @@ snapshots: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-minify-gradients@5.1.1(postcss@8.4.40): + dependencies: + colord: 2.9.3 + cssnano-utils: 3.1.0(postcss@8.4.40) + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-minify-params@5.1.4(postcss@8.4.39): dependencies: browserslist: 4.23.2 @@ -8466,15 +8737,31 @@ snapshots: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-minify-params@5.1.4(postcss@8.4.40): + dependencies: + browserslist: 4.23.2 + cssnano-utils: 3.1.0(postcss@8.4.40) + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-minify-selectors@5.2.1(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-selector-parser: 6.1.1 + postcss-minify-selectors@5.2.1(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-selector-parser: 6.1.1 + postcss-modules-extract-imports@3.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 + postcss-modules-extract-imports@3.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-modules-local-by-default@4.0.5(postcss@8.4.39): dependencies: icss-utils: 5.1.0(postcss@8.4.39) @@ -8482,16 +8769,33 @@ snapshots: postcss-selector-parser: 6.1.1 postcss-value-parser: 4.2.0 + postcss-modules-local-by-default@4.0.5(postcss@8.4.40): + dependencies: + icss-utils: 5.1.0(postcss@8.4.40) + postcss: 8.4.40 + postcss-selector-parser: 6.1.1 + postcss-value-parser: 4.2.0 + postcss-modules-scope@3.2.0(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-selector-parser: 6.1.1 + postcss-modules-scope@3.2.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-selector-parser: 6.1.1 + postcss-modules-values@4.0.0(postcss@8.4.39): dependencies: icss-utils: 5.1.0(postcss@8.4.39) postcss: 8.4.39 + postcss-modules-values@4.0.0(postcss@8.4.40): + dependencies: + icss-utils: 5.1.0(postcss@8.4.40) + postcss: 8.4.40 + postcss-modules@4.3.1(postcss@8.4.39): dependencies: generic-names: 4.0.0 @@ -8504,6 +8808,18 @@ snapshots: postcss-modules-values: 4.0.0(postcss@8.4.39) string-hash: 1.1.3 + postcss-modules@4.3.1(postcss@8.4.40): + dependencies: + generic-names: 4.0.0 + icss-replace-symbols: 1.1.0 + lodash.camelcase: 4.3.0 + postcss: 8.4.40 + postcss-modules-extract-imports: 3.1.0(postcss@8.4.40) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.40) + postcss-modules-scope: 3.2.0(postcss@8.4.40) + postcss-modules-values: 4.0.0(postcss@8.4.40) + string-hash: 1.1.3 + postcss-nested@5.0.6(postcss@8.4.39): dependencies: postcss: 8.4.39 @@ -8518,65 +8834,128 @@ snapshots: dependencies: postcss: 8.4.39 + postcss-normalize-charset@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-normalize-display-values@5.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-normalize-display-values@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-normalize-positions@5.1.1(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-normalize-positions@5.1.1(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-normalize-repeat-style@5.1.1(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-normalize-repeat-style@5.1.1(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-normalize-string@5.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-normalize-string@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-normalize-timing-functions@5.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-normalize-timing-functions@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-normalize-unicode@5.1.1(postcss@8.4.39): dependencies: browserslist: 4.23.2 postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-normalize-unicode@5.1.1(postcss@8.4.40): + dependencies: + browserslist: 4.23.2 + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-normalize-url@5.1.0(postcss@8.4.39): dependencies: normalize-url: 6.1.0 postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-normalize-url@5.1.0(postcss@8.4.40): + dependencies: + normalize-url: 6.1.0 + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-normalize-whitespace@5.1.1(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-normalize-whitespace@5.1.1(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-ordered-values@5.1.3(postcss@8.4.39): dependencies: cssnano-utils: 3.1.0(postcss@8.4.39) postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-ordered-values@5.1.3(postcss@8.4.40): + dependencies: + cssnano-utils: 3.1.0(postcss@8.4.40) + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-reduce-initial@5.1.2(postcss@8.4.39): dependencies: browserslist: 4.23.2 caniuse-api: 3.0.0 postcss: 8.4.39 + postcss-reduce-initial@5.1.2(postcss@8.4.40): + dependencies: + browserslist: 4.23.2 + caniuse-api: 3.0.0 + postcss: 8.4.40 + postcss-reduce-transforms@5.1.0(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-value-parser: 4.2.0 + postcss-reduce-transforms@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + postcss-reporter@7.1.0(postcss@8.4.39): dependencies: picocolors: 1.0.1 @@ -8594,11 +8973,22 @@ snapshots: postcss-value-parser: 4.2.0 svgo: 2.8.0 + postcss-svgo@5.1.0(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-value-parser: 4.2.0 + svgo: 2.8.0 + postcss-unique-selectors@5.1.1(postcss@8.4.39): dependencies: postcss: 8.4.39 postcss-selector-parser: 6.1.1 + postcss-unique-selectors@5.1.1(postcss@8.4.40): + dependencies: + postcss: 8.4.40 + postcss-selector-parser: 6.1.1 + postcss-value-parser@4.2.0: {} postcss@8.4.39: @@ -8607,6 +8997,12 @@ snapshots: picocolors: 1.0.1 source-map-js: 1.2.0 + postcss@8.4.40: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + prelude-ls@1.2.1: {} prettier@3.3.3: {} @@ -8810,7 +9206,26 @@ snapshots: transitivePeerDependencies: - ts-node - rollup-plugin-typescript2@0.31.2(rollup@2.79.1)(typescript@5.5.3): + rollup-plugin-postcss@4.0.2(postcss@8.4.40): + dependencies: + chalk: 4.1.2 + concat-with-sourcemaps: 1.1.0 + cssnano: 5.1.15(postcss@8.4.40) + import-cwd: 3.0.0 + p-queue: 6.6.2 + pify: 5.0.0 + postcss: 8.4.40 + postcss-load-config: 3.1.4(postcss@8.4.40) + postcss-modules: 4.3.1(postcss@8.4.40) + promise.series: 0.2.0 + resolve: 1.22.8 + rollup-pluginutils: 2.8.2 + safe-identifier: 0.4.2 + style-inject: 0.3.0 + transitivePeerDependencies: + - ts-node + + rollup-plugin-typescript2@0.31.2(rollup@2.79.1)(typescript@5.5.4): dependencies: '@rollup/pluginutils': 4.2.1 '@yarn-tool/resolve-package': 1.0.47 @@ -8819,9 +9234,9 @@ snapshots: resolve: 1.22.8 rollup: 2.79.1 tslib: 2.6.3 - typescript: 5.5.3 + typescript: 5.5.4 - rollup-plugin-typescript2@0.36.0(rollup@2.79.1)(typescript@5.5.3): + rollup-plugin-typescript2@0.36.0(rollup@2.79.1)(typescript@5.5.4): dependencies: '@rollup/pluginutils': 4.2.1 find-cache-dir: 3.3.2 @@ -8829,9 +9244,9 @@ snapshots: rollup: 2.79.1 semver: 7.6.2 tslib: 2.6.3 - typescript: 5.5.3 + typescript: 5.5.4 - rollup-plugin-typescript2@0.36.0(rollup@4.18.1)(typescript@5.5.3): + rollup-plugin-typescript2@0.36.0(rollup@4.18.1)(typescript@5.5.4): dependencies: '@rollup/pluginutils': 4.2.1 find-cache-dir: 3.3.2 @@ -8839,7 +9254,17 @@ snapshots: rollup: 4.18.1 semver: 7.6.2 tslib: 2.6.3 - typescript: 5.5.3 + typescript: 5.5.4 + + rollup-plugin-typescript2@0.36.0(rollup@4.20.0)(typescript@5.5.4): + dependencies: + '@rollup/pluginutils': 4.2.1 + find-cache-dir: 3.3.2 + fs-extra: 10.1.0 + rollup: 4.20.0 + semver: 7.6.2 + tslib: 2.6.3 + typescript: 5.5.4 rollup-pluginutils@2.8.2: dependencies: @@ -8871,6 +9296,28 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.18.1 fsevents: 2.3.3 + rollup@4.20.0: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.20.0 + '@rollup/rollup-android-arm64': 4.20.0 + '@rollup/rollup-darwin-arm64': 4.20.0 + '@rollup/rollup-darwin-x64': 4.20.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.20.0 + '@rollup/rollup-linux-arm-musleabihf': 4.20.0 + '@rollup/rollup-linux-arm64-gnu': 4.20.0 + '@rollup/rollup-linux-arm64-musl': 4.20.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0 + '@rollup/rollup-linux-riscv64-gnu': 4.20.0 + '@rollup/rollup-linux-s390x-gnu': 4.20.0 + '@rollup/rollup-linux-x64-gnu': 4.20.0 + '@rollup/rollup-linux-x64-musl': 4.20.0 + '@rollup/rollup-win32-arm64-msvc': 4.20.0 + '@rollup/rollup-win32-ia32-msvc': 4.20.0 + '@rollup/rollup-win32-x64-msvc': 4.20.0 + fsevents: 2.3.3 + run-async@2.4.1: {} run-parallel@1.2.0: @@ -9045,10 +9492,6 @@ snapshots: strip-json-comments@3.1.1: {} - strip-literal@2.1.0: - dependencies: - js-tokens: 9.0.0 - strong-log-transformer@2.1.0: dependencies: duplexer: 0.1.2 @@ -9063,6 +9506,12 @@ snapshots: postcss: 8.4.39 postcss-selector-parser: 6.1.1 + stylehacks@5.1.1(postcss@8.4.40): + dependencies: + browserslist: 4.23.2 + postcss: 8.4.40 + postcss-selector-parser: 6.1.1 + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -9160,11 +9609,13 @@ snapshots: through@2.3.8: {} - tinybench@2.8.0: {} + tinybench@2.9.0: {} + + tinypool@1.0.0: {} - tinypool@0.8.4: {} + tinyrainbow@1.2.0: {} - tinyspy@2.2.1: {} + tinyspy@3.0.0: {} tmp@0.0.33: dependencies: @@ -9188,9 +9639,9 @@ snapshots: trim-newlines@3.0.1: {} - ts-api-utils@1.3.0(typescript@5.5.3): + ts-api-utils@1.3.0(typescript@5.5.4): dependencies: - typescript: 5.5.3 + typescript: 5.5.4 ts-interface-checker@0.1.13: {} @@ -9216,8 +9667,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-detect@4.0.8: {} - type-fest@0.18.1: {} type-fest@0.20.2: {} @@ -9234,7 +9683,7 @@ snapshots: typescript@5.5.3: {} - ufo@1.5.4: {} + typescript@5.5.4: {} uglify-js@3.18.0: optional: true @@ -9293,13 +9742,13 @@ snapshots: validate-npm-package-name@5.0.1: {} - vite-node@1.6.0(@types/node@20.14.10): + vite-node@2.0.5(@types/node@20.14.10): dependencies: cac: 6.7.14 - debug: 4.3.5 + debug: 4.3.6 pathe: 1.1.2 - picocolors: 1.0.1 - vite: 5.3.4(@types/node@20.14.10) + tinyrainbow: 1.2.0 + vite: 5.3.5(@types/node@20.14.10) transitivePeerDependencies: - '@types/node' - less @@ -9319,27 +9768,35 @@ snapshots: '@types/node': 20.14.10 fsevents: 2.3.3 - vitest@1.6.0(@types/node@20.14.10): + vite@5.3.5(@types/node@20.14.10): dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - acorn-walk: 8.3.3 - chai: 4.4.1 - debug: 4.3.5 + esbuild: 0.21.5 + postcss: 8.4.40 + rollup: 4.20.0 + optionalDependencies: + '@types/node': 20.14.10 + fsevents: 2.3.3 + + vitest@2.0.5(@types/node@20.14.10): + dependencies: + '@ampproject/remapping': 2.3.0 + '@vitest/expect': 2.0.5 + '@vitest/pretty-format': 2.0.5 + '@vitest/runner': 2.0.5 + '@vitest/snapshot': 2.0.5 + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + debug: 4.3.6 execa: 8.0.1 - local-pkg: 0.5.0 - magic-string: 0.30.10 + magic-string: 0.30.11 pathe: 1.1.2 - picocolors: 1.0.1 std-env: 3.7.0 - strip-literal: 2.1.0 - tinybench: 2.8.0 - tinypool: 0.8.4 - vite: 5.3.4(@types/node@20.14.10) - vite-node: 1.6.0(@types/node@20.14.10) + tinybench: 2.9.0 + tinypool: 1.0.0 + tinyrainbow: 1.2.0 + vite: 5.3.5(@types/node@20.14.10) + vite-node: 2.0.5(@types/node@20.14.10) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.14.10 @@ -9478,8 +9935,6 @@ snapshots: yocto-queue@0.1.0: {} - yocto-queue@1.1.1: {} - zustand@4.5.4(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1): dependencies: use-sync-external-store: 1.2.0(react@18.3.1) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d35b34d..149540f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,5 +5,5 @@ packages: - packages/ui-testing - packages/dev-server # - - game-template/game - - game-template/ui + - games/*/game + - games/*/ui