diff --git a/.adr-dir b/.adr-dir new file mode 100644 index 00000000..0d38988a --- /dev/null +++ b/.adr-dir @@ -0,0 +1 @@ +doc/architecture/decisions diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 90b140e3..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,157 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true, - "node": true - }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], - "parser": "@typescript-eslint/parser", - - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module", - "project": ["tsconfig.json"] - }, - "ignorePatterns": ["**/*.typegen.ts"], - "plugins": ["@typescript-eslint", "import", "no-only-tests", "sort-exports"], - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [".ts", ".tsx"] - }, - "import/resolver": { - "node": { - "extensions": [".js", ".jsx", ".ts", ".tsx", ".json"] - }, - "typescript-bun": { - "alwaysTryTypes": true - } - } - }, - "rules": { - "@typescript-eslint/array-type": ["error", { "default": "generic" }], - "@typescript-eslint/consistent-indexed-object-style": "error", - "@typescript-eslint/consistent-type-exports": "error", - "@typescript-eslint/consistent-type-imports": "error", - "@typescript-eslint/member-ordering": [ - "error", - { - "default": { - "memberTypes": [ - "public-static-method", - "protected-static-method", - "private-static-method", - "static-field", - "instance-field", - "constructor", - ["public-get", "public-set"], - "public-instance-method", - ["protected-get", "protected-set"], - "protected-instance-method", - ["private-get", "private-set"], - "private-instance-method" - ], - "order": "natural-case-insensitive" - }, - "interfaces": { - "memberTypes": ["field", "signature", "constructor", "method"], - "optionalityOrder": "required-first", - "order": "natural-case-insensitive" - }, - "typeLiterals": { - "memberTypes": ["field", "signature", "constructor", "method"], - "optionalityOrder": "required-first", - "order": "natural-case-insensitive" - } - } - ], - "@typescript-eslint/no-unused-vars": [ - "warn", - { - "argsIgnorePattern": "res|next|^err|_", - "ignoreRestSiblings": true - } - ], - "@typescript-eslint/no-shadow": "error", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/prefer-destructuring": [ - "error", - { - "array": false, - "object": true - }, - { - "enforceForRenamedProperties": false - } - ], - "@typescript-eslint/sort-type-constituents": "error", - "class-methods-use-this": "off", - "comma-dangle": ["error", "always-multiline"], - "import/exports-last": "error", - "import/extensions": [ - "error", - "ignorePackages", - { - "js": "never", - "mjs": "never", - "jsx": "never", - "ts": "never", - "tsx": "never" - } - ], - "import/first": "error", - "import/group-exports": "error", - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.test.js", - "**/*.test.ts", - "**/testutils/**", - "cypress.config.ts" - ] - } - ], - "import/no-unresolved": "error", - "import/order": [ - "error", - { - "alphabetize": { "order": "asc", "orderImportKind": "asc" }, - "groups": [["builtin", "external"]], - "newlines-between": "always" - } - ], - "no-empty-function": [ - "warn", - { - "allow": ["constructors"] - } - ], - "no-only-tests/no-only-tests": "error", - "no-shadow": "off", - "no-unused-vars": [ - "error", - { - "argsIgnorePattern": "res|next|^err|_", - "ignoreRestSiblings": true - } - ], - "no-use-before-define": "off", - "no-useless-constructor": "off", - "prettier/prettier": [ - "error", - { - "trailingComma": "all", - "singleQuote": true, - "printWidth": 120 - } - ], - "sort-exports/sort-exports": ["error", { "sortExportKindFirst": "value" }], - "sort-imports": [ - "error", - { - "ignoreDeclarationSort": true - } - ], - "sort-keys": ["error", "asc", { "caseSensitive": true, "natural": false }] - } -} diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..2dbbe00e --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +20.11.1 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..1d085cac --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +** diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 5b5bd993..00000000 --- a/.prettierrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "proseWrap": "always" -} diff --git a/.stylelintrc.json b/.stylelintrc.json index 8d136409..0843ca1d 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,3 +1,3 @@ { - "extends": ["stylelint-config-standard", "stylelint-config-clean-order"] + "extends": ["stylelint-config-standard", "stylelint-config-clean-order"] } diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..68895e1c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "biomejs.biome", + "ms-playwright.playwright", + "oven.bun-vscode", + "statelyai.stately-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..79c758d3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.codeActionsOnSave": { + "quickfix.biome": "always", + "source.organizeImports.biome": "explicit" + }, + "[javascript][typescript][json]": { + "editor.defaultFormatter": "biomejs.biome" + } +} diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..66d479c0 --- /dev/null +++ b/biome.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "files": { + "ignore": ["**/*.typegen.ts"] + }, + "linter": { + "enabled": true, + "rules": { + "all": true, + "nursery": { + "all": false + } + } + }, + "organizeImports": { + "enabled": true + }, + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + } +} diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 00000000..1fe90030 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[install.lockfile] +print = "yarn" \ No newline at end of file diff --git a/client/index.ts b/client/index.ts index a0554e6a..90c0afa4 100644 --- a/client/index.ts +++ b/client/index.ts @@ -1,48 +1,49 @@ import { io } from "socket.io-client"; -import { NameFormElement } from "../server/@types/ui"; import { Player, Question } from "../server/@types/models"; +import { NameFormElement } from "../server/@types/ui"; import { getElementById } from "./utils/getElementById"; const addPlayer = async (name: string): Promise => { - socket.emit("players:post", { name }); + socket.emit("players:post", { name }); }; const generateSocketUrl = (): string => { - const location = window.location; + const location = window.location; - return "//" + location.host + location.pathname; + return `//${location.host}${location.pathname}`; }; const renderPlayerList = (): void => { - const html = players.map((name) => `
  • ${name}
  • `); - playerListElement.innerHTML = html.join("\n"); + const html = players.map((name) => `
  • ${name}
  • `); + playerListElement.innerHTML = html.join("\n"); }; const renderPlayerName = (): void => { - const text = `Name: ${player.name}`; - playerNameElement.innerText = text; + const text = `Name: ${player.name}`; + playerNameElement.innerText = text; }; +// biome-ignore lint/style/useNamingConvention: the issue here is the consecutive upper case characters, but given it's due to using a single-character word, this doesn't feel invalid const askAQuestion = (data: Question): void => { - const { question, number } = data; - const questionHtml = getElementById("question"); - questionHtml.innerText = question; - const numberHtml = getElementById("number"); - numberHtml.innerText = number.toString(); + const { question, number } = data; + const questionHtml = getElementById("question"); + questionHtml.innerText = question; + const numberHtml = getElementById("number"); + numberHtml.innerText = number.toString(); }; const derenderNameForm = (): void => { - getElementById("name-form").remove(); + getElementById("name-form").remove(); }; const startButton = getElementById("start-button"); const showStartButton = (): void => { - startButton.style.display = "block"; + startButton.style.display = "block"; }; startButton.addEventListener("click", () => { - socket.emit("round:start"); + socket.emit("round:start"); }); const connectionStatusIconElement = getElementById("connection-status-icon"); @@ -51,39 +52,39 @@ const playerListElement = getElementById("player-list"); const playerNameElement = getElementById("player-name"); let player: Player; -let players: Array = []; +let players: Player[] = []; const socket = io(generateSocketUrl()); socket.on("connect", () => { - connectionStatusIconElement.innerText = "Connected 🟢"; + connectionStatusIconElement.innerText = "Connected 🟢"; }); socket.on("disconnect", () => { - connectionStatusIconElement.innerText = "Disconnected 🔴"; + connectionStatusIconElement.innerText = "Disconnected 🔴"; }); socket.on("players:get", (data) => { - players = data.players; - renderPlayerList(); + players = data.players; + renderPlayerList(); }); socket.on("player:set", (data) => { - player = data.player; - renderPlayerName(); - derenderNameForm(); + player = data.player; + renderPlayerName(); + derenderNameForm(); }); socket.on("question:get", (data) => { - askAQuestion(data.question); + askAQuestion(data.question); }); socket.on("game:startable", () => { - showStartButton(); + showStartButton(); }); -nameFormElement.addEventListener("submit", function (e) { - e.preventDefault(); - addPlayer(nameFormElement.elements.name.value); - nameFormElement.elements.name.value = ""; +nameFormElement.addEventListener("submit", (e) => { + e.preventDefault(); + addPlayer(nameFormElement.elements.name.value); + nameFormElement.elements.name.value = ""; }); diff --git a/client/utils/getElementById.ts b/client/utils/getElementById.ts index ca1825e8..fc78a2c4 100644 --- a/client/utils/getElementById.ts +++ b/client/utils/getElementById.ts @@ -1,9 +1,9 @@ export const getElementById = (id: HTMLElement["id"]): HTMLElement => { - const element = document.getElementById(id); + const element = document.getElementById(id); - if (!element) { - throw new Error(`No element found with ID: ${id}`); - } + if (!element) { + throw new Error(`No element found with ID: ${id}`); + } - return element; + return element; }; diff --git a/doc/architecture/decisions/0001-record-architecture-decisions.md b/doc/architecture/decisions/0001-record-architecture-decisions.md new file mode 100644 index 00000000..c4657844 --- /dev/null +++ b/doc/architecture/decisions/0001-record-architecture-decisions.md @@ -0,0 +1,19 @@ +# 1. Record architecture decisions + +Date: 2023-10-24 + +## Status + +Accepted + +## Context + +We need to record the architectural decisions made on this project. + +## Decision + +We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools). diff --git a/docs/decisions/0001-use-bun.md b/doc/architecture/decisions/0002-use-bun.md similarity index 99% rename from docs/decisions/0001-use-bun.md rename to doc/architecture/decisions/0002-use-bun.md index 03470bcc..49829fdc 100644 --- a/docs/decisions/0001-use-bun.md +++ b/doc/architecture/decisions/0002-use-bun.md @@ -1,4 +1,4 @@ -# 1. Use Bun +# 2. Use Bun Date: 2023-10-24 diff --git a/docs/decisions/0002-use-game-class-to-interact-with-state-machine.md b/doc/architecture/decisions/0003-use-game-class-to-interact-with-state-machine.md similarity index 94% rename from docs/decisions/0002-use-game-class-to-interact-with-state-machine.md rename to doc/architecture/decisions/0003-use-game-class-to-interact-with-state-machine.md index e5f1ec05..4c105d95 100644 --- a/docs/decisions/0002-use-game-class-to-interact-with-state-machine.md +++ b/doc/architecture/decisions/0003-use-game-class-to-interact-with-state-machine.md @@ -1,4 +1,4 @@ -# 1. Use Game class to interact with state machine +# 3. Use Game class to interact with state machine Date: 2023-11-20 diff --git a/doc/architecture/decisions/0004-use-biome.md b/doc/architecture/decisions/0004-use-biome.md new file mode 100644 index 00000000..80890a51 --- /dev/null +++ b/doc/architecture/decisions/0004-use-biome.md @@ -0,0 +1,53 @@ +# 4. Use Biome + +Date: 2024-02-14 + +## Status + +Accepted + +## Context + +As documented in the dxw tech team RFC [Use linting tools across all of our +projects][linters], we use linters and formatters to ensure a standard approach +to the way our code is structured within files. + +For JavaScript and TypeScript, we often use some combination of [ESLint][], +[Standard][], and [Prettier][] for these purposes, and had been using ESLint and +Prettier here. However, we've encountered issues getting this to play nice with +[Bun][] in various local development environments, and experiments with Standard +have also proved problematic. + +While setting the codebase up to work with a particular set of rules is often +the bulk of the work with linters (rather than ongoing maintenance), the issues +we've encountered on this project have taken time away from more interesting +experiments with state machines, WebSockets, and so on. + +[Biome][] is a relatively new linter and formatter. Having been built in Rust, +it's highly performant. It provides a substantial set of [linting rules][] by +default, [many of which come from ESLint or ESLint plugins][linting rule +sources], and has a similar philosophy to Prettier on [opinionated formatting][] +(limited configuration). + +## Decision + +We will use Biome for linting and formatting our TypeScript and JSON files. + +## Consequences + +- We will have fewer decisions to make about what linting and formatting rules + to apply +- We may be unable to use some of the more niche ESLint rules or third-party + plugins +- We should be able to revert/switch to another linter/formatter if we encounter + issues + +[Biome]: https://biomejs.dev +[Bun]: ./0002-use-bun.md +[ESLint]: https://github.com/search?q=org:dxw+%22eslint%22:+path:package.json&type=code +[linters]: https://github.com/dxw/tech-team-rfcs/blob/main/tools-and-technology/rfc-035-use-linting-tools-across-all-our-projects.md +[linting rule sources]: https://biomejs.dev/linter/rules-sources/#rules-from-other-sources +[linting rules]: https://biomejs.dev/linter/rules +[opinionated formatting]: https://biomejs.dev/formatter +[Prettier]: https://github.com/search?q=org:dxw+%22prettier%22:+path:package.json&type=code +[Standard]: https://github.com/search?q=org%3Adxw+%22standard%22%3A+path%3Apackage.json&type=code diff --git a/e2e/app.test.ts b/e2e/app.test.ts index aa89ae0b..51b4d294 100644 --- a/e2e/app.test.ts +++ b/e2e/app.test.ts @@ -1,23 +1,23 @@ -import { test, expect } from "@playwright/test"; +import { expect, test } from "@playwright/test"; test("test application", async ({ page }) => { - await page.goto("/"); + await page.goto("/"); - // Verify page title - await expect(page).toHaveTitle("Colour Me Knowledgeable!"); + // Verify page title + await expect(page).toHaveTitle("Colour Me Knowledgeable!"); - // Verify colour is visible - await page.getByText("Connected 🟢"); + // Verify colour is visible + await page.getByText("Connected 🟢"); - // Enter name - const displayNameInput = await page.getByLabel("Display name"); - displayNameInput.click(); - displayNameInput.fill("Test name"); + // Enter name + const displayNameInput = await page.getByLabel("Display name"); + displayNameInput.click(); + displayNameInput.fill("Test name"); - // Click join - await page.getByRole("button", { name: "Join game" }).click(); + // Click join + await page.getByRole("button", { name: "Join game" }).click(); - // Verify name is visible - await page.getByText("Name: Test name"); - await page.getByText("Test name", { exact: true }); + // Verify name is visible + await page.getByText("Name: Test name"); + await page.getByText("Test name", { exact: true }); }); diff --git a/package.json b/package.json index 86225045..15cce9c1 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "license": "MIT", "scripts": { "compile": "bun build ./client/index.ts --outdir ./client/assets/scripts", - "lint:ts": "eslint ./server --cache --max-warnings 0 --ignore-pattern node_modules", - "lint:ts:fix": "eslint ./server --cache --max-warnings 0 --fix", + "lint:ts": "bunx @biomejs/biome check .", + "lint:ts:fix": "bunx @biomejs/biome check --apply .", "lint:css": "bunx stylelint \"client/assets/styles/*.css\"", "lint:css:fix": "bunx stylelint \"client/assets/styles/*.css\" --fix", "start": "bun run compile && bun server/index.ts --watch", @@ -22,20 +22,11 @@ "xstate": "4.38.3" }, "devDependencies": { + "@biomejs/biome": "1.5.3", "@playwright/test": "^1.39.0", "@types/serve-handler": "^6.1.1", "@types/web": "^0.0.138", - "@typescript-eslint/eslint-plugin": "^6.9.0", - "@typescript-eslint/parser": "^6.9.0", "bun-types": "^1.0.2", - "eslint": "^8.52.0", - "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript-bun": "^0.0.73", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-no-only-tests": "^3.1.0", - "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-sort-exports": "^0.8.0", - "prettier": "^3.0.3", "socket.io-client": "^4.7.2", "stylelint": "^16.0.0", "stylelint-config-clean-order": "^5.2.0", diff --git a/playwright.config.ts b/playwright.config.ts index 6e04c3cb..ac27f04b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,42 +1,44 @@ import { defineConfig, devices } from "@playwright/test"; +// biome-ignore lint/style/noDefaultExport: this needs to be in the format expected by PlayWright export default defineConfig({ - // Look for test files in the "tests" directory, relative to this configuration file. - testDir: "e2e", - - // Run all tests in parallel. - fullyParallel: true, - - // Fail the build on CI if you accidentally left test.only in the source code. - forbidOnly: !!process.env.CI, - - // Retry on CI only. - retries: process.env.CI ? 2 : 0, - - // Opt out of parallel tests on CI. - workers: process.env.CI ? 1 : undefined, - - // Reporter to use - reporter: "html", - - use: { - // Base URL to use in actions like `await page.goto('/')`. - baseURL: "http://localhost:8080", - - // Collect trace when retrying the failed test. - trace: "on-first-retry", - }, - // Configure projects for major browsers. - projects: [ - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, - }, - ], - - webServer: { - command: "bun run start", - url: "http://localhost:8080", - reuseExistingServer: !process.env.CI, - }, + // Look for test files in the "tests" directory, relative to this configuration file. + testDir: "e2e", + + // Run all tests in parallel. + fullyParallel: true, + + // Fail the build on CI if you accidentally left test.only in the source code. + forbidOnly: !!process.env.CI, + + // Retry on CI only. + retries: process.env.CI ? 2 : 0, + + // Opt out of parallel tests on CI. + workers: process.env.CI ? 1 : undefined, + + // Reporter to use + reporter: "html", + + use: { + // Base URL to use in actions like `await page.goto('/')`. + // biome-ignore lint/style/useNamingConvention: the issue here is the consecutive upper case characters, but we need to follow what the library expects in this case + baseURL: "http://localhost:8080", + + // Collect trace when retrying the failed test. + trace: "on-first-retry", + }, + // Configure projects for major browsers. + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + + webServer: { + command: "bun run start", + url: "http://localhost:8080", + reuseExistingServer: !process.env.CI, + }, }); diff --git a/renovate.json b/renovate.json index 4bd832f5..7ec37f6f 100644 --- a/renovate.json +++ b/renovate.json @@ -1,4 +1,4 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:base"] + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:base"] } diff --git a/server/@types/models/index.d.ts b/server/@types/models/index.d.ts index 24f1e42a..4ebc9229 100644 --- a/server/@types/models/index.d.ts +++ b/server/@types/models/index.d.ts @@ -1,12 +1,12 @@ type Player = { - name: string; - socketId: string; + name: string; + socketId: string; }; type Question = { - answer: Array; - number: number; - question: string; + answer: string[]; + number: number; + question: string; }; export type { Player, Question }; diff --git a/server/@types/ui/index.d.ts b/server/@types/ui/index.d.ts index e3587e2c..3348de2b 100644 --- a/server/@types/ui/index.d.ts +++ b/server/@types/ui/index.d.ts @@ -1,9 +1,9 @@ type NameFormElementElements = HTMLFormControlsCollection & { - name: HTMLInputElement; + name: HTMLInputElement; }; type NameFormElement = HTMLFormElement & { - elements: NameFormElementElements; + elements: NameFormElementElements; }; export type { NameFormElement }; diff --git a/server/data/questions.json b/server/data/questions.json index e44812af..567b8466 100644 --- a/server/data/questions.json +++ b/server/data/questions.json @@ -1,17 +1,17 @@ [ - { - "answer": ["black", "orange", "yellow", "white"], - "number": 4, - "question": "duxw" - }, - { - "answer": ["green", "red", "white"], - "number": 3, - "question": "Caprese salad" - }, - { - "answer": ["blue", "red", "yellow"], - "number": 3, - "question": "Moldovan flag" - } + { + "answer": ["black", "orange", "yellow", "white"], + "number": 4, + "question": "duxw" + }, + { + "answer": ["green", "red", "white"], + "number": 3, + "question": "Caprese salad" + }, + { + "answer": ["blue", "red", "yellow"], + "number": 3, + "question": "Moldovan flag" + } ] diff --git a/server/events/clientbound.ts b/server/events/clientbound.ts index b4f57268..8488f18c 100644 --- a/server/events/clientbound.ts +++ b/server/events/clientbound.ts @@ -1,20 +1,19 @@ -import type { Player, Question } from '../@types/models'; -import type Lobby from '../lobby'; +import type { Player, Question } from "../@types/models"; +import type { Lobby } from "../lobby"; -export default class ClientboundEvents { - static getPlayers(lobby: Lobby): [string, { players: Array }] { - return ['players:get', { players: lobby.playerNames() }]; - } +const clientboundEvents = { + getPlayers: (lobby: Lobby): [string, { players: Player["name"][] }] => { + return ["players:get", { players: lobby.playerNames() }]; + }, + getQuestion: (question: Question): [string, { question: Question }] => { + return ["question:get", { question }]; + }, + setPlayer: (player: Player): [string, { player: Player }] => { + return ["player:set", { player }]; + }, + showStartButton: (): string => { + return "game:startable"; + }, +}; - static getQuestion(question: Question): [string, { question: Question }] { - return ['question:get', { question }]; - } - - static setPlayer(player: Player): [string, { player: Player }] { - return ['player:set', { player }]; - } - - static showStartButton(): string { - return 'game:startable'; - } -} +export { clientboundEvents }; diff --git a/server/events/severbound.ts b/server/events/severbound.ts index b0f81d2a..45c1a7b6 100644 --- a/server/events/severbound.ts +++ b/server/events/severbound.ts @@ -1,38 +1,46 @@ -import type { Server, Socket } from 'socket.io'; +import type { Server, Socket } from "socket.io"; -import ClientboundEvents from './clientbound'; -import type Lobby from '../lobby'; -import type { SocketServer } from '../socketServer'; +import type { Lobby } from "../lobby"; +import type { SocketServer } from "../socketServer"; +import { clientboundEvents } from "./clientbound"; -export default class ServerboundEvents { - static disconnect(lobby: Lobby, socket: Socket, server: Server): [string, () => void] { - return [ - 'disconnect', - () => { - console.info(`disconnected: ${socket.id}`); - lobby.removePlayer(socket.id); - server.emit(...ClientboundEvents.getPlayers(lobby)); - }, - ]; - } +const serverboundEvents = { + disconnect: ( + lobby: Lobby, + socket: Socket, + server: Server, + ): [string, () => void] => { + return [ + "disconnect", + () => { + console.info(`disconnected: ${socket.id}`); + lobby.removePlayer(socket.id); + server.emit(...clientboundEvents.getPlayers(lobby)); + }, + ]; + }, + postPlayers: ( + lobby: Lobby, + socket: Socket, + server: Server, + ): [string, (data: { name: string }) => void] => { + return [ + "players:post", + (data: { name: string }) => { + const player = lobby.addPlayer(data.name, socket.id); + socket.emit(...clientboundEvents.setPlayer(player)); + server.emit(...clientboundEvents.getPlayers(lobby)); + }, + ]; + }, + startRound: (server: SocketServer): [string, () => void] => { + return [ + "round:start", + () => { + server.onRoundStarted(); + }, + ]; + }, +}; - static postPlayers(lobby: Lobby, socket: Socket, server: Server): [string, (data: { name: string }) => void] { - return [ - 'players:post', - (data: { name: string }) => { - const player = lobby.addPlayer(data.name, socket.id); - socket.emit(...ClientboundEvents.setPlayer(player)); - server.emit(...ClientboundEvents.getPlayers(lobby)); - }, - ]; - } - - static startRound(server: SocketServer): [string, () => void] { - return [ - 'round:start', - () => { - server.onRoundStarted(); - }, - ]; - } -} +export { serverboundEvents }; diff --git a/server/index.ts b/server/index.ts index 82b863a3..e454afa4 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,18 +1,20 @@ -import http from 'http'; -import handler from 'serve-handler'; +import http from "http"; +import handler from "serve-handler"; -import { SocketServer } from './socketServer'; +import { SocketServer } from "./socketServer"; const httpServer = http.createServer((request, response) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return handler(request as any, response as any, { - public: './client', - }); + // biome-ignore lint/suspicious/noExplicitAny: we don't have a fix for this yet + return handler(request as any, response as any, { + public: "./client", + }); }); -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: we don't have a fix for this yet new SocketServer(httpServer as any); const port = process.env.PORT || 8080; -httpServer.listen(port, () => console.info(`Server running at http://localhost:${port}`)); +httpServer.listen(port, () => + console.info(`Server running at http://localhost:${port}`), +); diff --git a/server/lobby.ts b/server/lobby.ts index 2fe80ad9..cf765938 100644 --- a/server/lobby.ts +++ b/server/lobby.ts @@ -1,60 +1,65 @@ -import type { Socket } from 'socket.io'; -import type { InterpreterFrom } from 'xstate'; -import { interpret } from 'xstate'; - -import type { Player } from './@types/models'; -import { context, lobbyMachine } from './machines/lobby'; -import type { SocketServer } from './socketServer'; - -export default class Lobby { - machine: InterpreterFrom; - server: SocketServer; - - constructor(server: SocketServer) { - this.server = server; - this.machine = interpret(lobbyMachine.withContext({ ...context })).start(); - this.machine.start(); - this.machine.onTransition((state) => { - console.info({ context: state.context, state: state.value }); - - switch (state.value) { - case 'MultiplePlayers': - this.emitShowStartButton(); - break; - default: - break; - } - }); - } - - addPlayer = (name: Player['name'], socketId: Socket['id']) => { - const player = { name, socketId }; - this.machine.send({ player, type: 'playerJoins' }); - return this.findPlayer(player); - }; - - emitShowStartButton = (): void => { - this.server.onShowStartButton(); - }; - - findPlayer = (player: Player): Player => { - const desiredPlayer = this.machine.getSnapshot().context.players.find((p) => p.socketId === player.socketId); - - if (!desiredPlayer) { - throw new Error('Player not found in context'); - } - - return desiredPlayer; - }; - - playerNames = (): Array => { - return this.machine - .getSnapshot() - .context.players.map((player) => player.name) - .reverse(); - }; - - removePlayer = (socketId: Socket['id']): void => { - this.machine.send({ socketId, type: 'playerLeaves' }); - }; +import type { Socket } from "socket.io"; +import type { InterpreterFrom } from "xstate"; +import { interpret } from "xstate"; + +import type { Player } from "./@types/models"; +import { context, lobbyMachine } from "./machines/lobby"; +import type { SocketServer } from "./socketServer"; + +class Lobby { + machine: InterpreterFrom; + server: SocketServer; + + constructor(server: SocketServer) { + this.server = server; + this.machine = interpret(lobbyMachine.withContext({ ...context })).start(); + this.machine.start(); + this.machine.onTransition((state) => { + console.info({ context: state.context, state: state.value }); + + switch (state.value) { + case "multiplePlayers": { + this.emitShowStartButton(); + break; + } + default: + break; + } + }); + } + + addPlayer = (name: Player["name"], socketId: Socket["id"]) => { + const player = { name, socketId }; + this.machine.send({ player, type: "playerJoins" }); + return this.findPlayer(player); + }; + + emitShowStartButton = (): void => { + this.server.onShowStartButton(); + }; + + findPlayer = (player: Player): Player => { + const desiredPlayer = this.machine + .getSnapshot() + .context.players.find((p) => p.socketId === player.socketId); + + if (!desiredPlayer) { + throw new Error("Player not found in context"); + } + + return desiredPlayer; + }; + + playerNames = (): Player["name"][] => { + return this.machine + .getSnapshot() + .context.players.map((player) => player.name) + .reverse(); + }; + + removePlayer = (socketId: Socket["id"]): void => { + this.machine.send({ socketId, type: "playerLeaves" }); + }; } + +export { Lobby }; diff --git a/server/machines/lobby.test.ts b/server/machines/lobby.test.ts index 5b682972..9d5c8c6d 100644 --- a/server/machines/lobby.test.ts +++ b/server/machines/lobby.test.ts @@ -1,144 +1,148 @@ -import { beforeEach, describe, expect, it } from 'bun:test'; -import type { InterpreterFrom } from 'xstate'; -import { interpret } from 'xstate'; - -import { context, isNewPlayer, lobbyMachine } from './lobby'; - -describe('lobbyMachine states', () => { - const player1 = { name: 'a name', socketId: 'id' }; - const player2 = { name: 'a name 2', socketId: 'id-2' }; - const player3 = { name: 'a name 3', socketId: 'id-3' }; - - describe('Empty', () => { - it('transitions to the OnePlayer state when it receives the player joins event', () => { - expect(lobbyMachine.transition('Empty', 'playerJoins').value).toBe('OnePlayer'); - }); - }); - - describe('OnePlayer', () => { - let actor: InterpreterFrom; - - beforeEach(() => { - actor = interpret(lobbyMachine); - actor.start(); - actor.send({ - player: player1, - type: 'playerJoins', - }); - }); - - it('transitions to the MultiplePlayers state when it receives two player joins events', () => { - actor.send({ - player: player2, - type: 'playerJoins', - }); - - expect(actor.getSnapshot().value).toBe('MultiplePlayers'); - expect(actor.getSnapshot().context).toEqual({ - ...context, - players: [player1, player2], - }); - }); - - it('transitions from OnePlayer to Empty state when it receives player leaves event', () => { - expect(lobbyMachine.transition('OnePlayer', 'playerLeaves').value).toBe('Empty'); - }); - - it('removes a player from the player list when it receives playerLeaves event', () => { - actor.send({ - socketId: player1.socketId, - type: 'playerLeaves', - }); - - expect(actor.getSnapshot().context.players.length).toEqual(0); - }); - }); - - describe('MultiplePlayers', () => { - let actor: InterpreterFrom; - - beforeEach(() => { - actor = interpret(lobbyMachine); - actor.start(); - - actor.send({ - player: player1, - type: 'playerJoins', - }); - - actor.send({ - player: player2, - type: 'playerJoins', - }); - }); - - it('transitions from the MultiplePlayers state to the OnePlayer state when it receives a playerLeaves event', () => { - actor.send({ - socketId: player2.socketId, - type: 'playerLeaves', - }); - - expect(actor.getSnapshot().value).toBe('OnePlayer'); - }); - - it('adds more than two players', () => { - actor.send({ - player: player3, - type: 'playerJoins', - }); - - expect(actor.getSnapshot().value).toBe('MultiplePlayers'); - expect(actor.getSnapshot().context).toEqual({ - ...context, - players: [player1, player2, player3], - }); - }); - - it('transitions to OnePlayer if there is only one player left when playerLeaves', () => { - actor.send({ - socketId: player1.socketId, - type: 'playerLeaves', - }); - - expect(actor.getSnapshot().value).toBe('OnePlayer'); - }); - - it('does not transition to OnePlayer if there is more than one player left when playerLeaves', () => { - actor.send({ - player: player3, - type: 'playerJoins', - }); - - actor.send({ - socketId: player1.socketId, - type: 'playerLeaves', - }); - - expect(actor.getSnapshot().value).toBe('MultiplePlayers'); - }); - - it('removes a player from the player list when it receives playerLeaves event', () => { - actor.send({ - socketId: player1.socketId, - type: 'playerLeaves', - }); - - expect(actor.getSnapshot().context.players).toEqual([player2]); - }); - }); +import { beforeEach, describe, expect, it } from "bun:test"; +import type { InterpreterFrom } from "xstate"; +import { interpret } from "xstate"; + +import { context, isNewPlayer, lobbyMachine } from "./lobby"; + +describe("lobbyMachine states", () => { + const player1 = { name: "a name", socketId: "id" }; + const player2 = { name: "a name 2", socketId: "id-2" }; + const player3 = { name: "a name 3", socketId: "id-3" }; + + describe("empty", () => { + it("transitions to the onePlayer state when it receives the player joins event", () => { + expect(lobbyMachine.transition("empty", "playerJoins").value).toBe( + "onePlayer", + ); + }); + }); + + describe("onePlayer", () => { + let actor: InterpreterFrom; + + beforeEach(() => { + actor = interpret(lobbyMachine); + actor.start(); + actor.send({ + player: player1, + type: "playerJoins", + }); + }); + + it("transitions to the multiplePlayers state when it receives two player joins events", () => { + actor.send({ + player: player2, + type: "playerJoins", + }); + + expect(actor.getSnapshot().value).toBe("multiplePlayers"); + expect(actor.getSnapshot().context).toEqual({ + ...context, + players: [player1, player2], + }); + }); + + it("transitions from onePlayer to empty state when it receives player leaves event", () => { + expect(lobbyMachine.transition("onePlayer", "playerLeaves").value).toBe( + "empty", + ); + }); + + it("removes a player from the player list when it receives playerLeaves event", () => { + actor.send({ + socketId: player1.socketId, + type: "playerLeaves", + }); + + expect(actor.getSnapshot().context.players.length).toEqual(0); + }); + }); + + describe("multiplePlayers", () => { + let actor: InterpreterFrom; + + beforeEach(() => { + actor = interpret(lobbyMachine); + actor.start(); + + actor.send({ + player: player1, + type: "playerJoins", + }); + + actor.send({ + player: player2, + type: "playerJoins", + }); + }); + + it("transitions from the multiplePlayers state to the onePlayer state when it receives a playerLeaves event", () => { + actor.send({ + socketId: player2.socketId, + type: "playerLeaves", + }); + + expect(actor.getSnapshot().value).toBe("onePlayer"); + }); + + it("adds more than two players", () => { + actor.send({ + player: player3, + type: "playerJoins", + }); + + expect(actor.getSnapshot().value).toBe("multiplePlayers"); + expect(actor.getSnapshot().context).toEqual({ + ...context, + players: [player1, player2, player3], + }); + }); + + it("transitions to onePlayer if there is only one player left when playerLeaves", () => { + actor.send({ + socketId: player1.socketId, + type: "playerLeaves", + }); + + expect(actor.getSnapshot().value).toBe("onePlayer"); + }); + + it("does not transition to onePlayer if there is more than one player left when playerLeaves", () => { + actor.send({ + player: player3, + type: "playerJoins", + }); + + actor.send({ + socketId: player1.socketId, + type: "playerLeaves", + }); + + expect(actor.getSnapshot().value).toBe("multiplePlayers"); + }); + + it("removes a player from the player list when it receives playerLeaves event", () => { + actor.send({ + socketId: player1.socketId, + type: "playerLeaves", + }); + + expect(actor.getSnapshot().context.players).toEqual([player2]); + }); + }); }); -describe('isNewPlayer', () => { - it('returns true if the player is not present in the players array', () => { - const player = { name: 'a name', socketId: 'id' }; - const contextWithNoPlayers = { players: [] }; - expect(isNewPlayer(contextWithNoPlayers, { player })).toBe(true); - }); +describe("isNewPlayer", () => { + it("returns true if the player is not present in the players array", () => { + const player = { name: "a name", socketId: "id" }; + const contextWithNoPlayers = { players: [] }; + expect(isNewPlayer(contextWithNoPlayers, { player })).toBe(true); + }); - it('returns false if the player is present in the players array', () => { - const player = { name: 'a name', socketId: 'id' }; - const contextWithPlayer = { players: [player] }; + it("returns false if the player is present in the players array", () => { + const player = { name: "a name", socketId: "id" }; + const contextWithPlayer = { players: [player] }; - expect(isNewPlayer(contextWithPlayer, { player })).toBe(false); - }); + expect(isNewPlayer(contextWithPlayer, { player })).toBe(false); + }); }); diff --git a/server/machines/lobby.ts b/server/machines/lobby.ts index ded23602..ba7f97bc 100644 --- a/server/machines/lobby.ts +++ b/server/machines/lobby.ts @@ -1,86 +1,91 @@ -import { assign, createMachine } from 'xstate'; +import { assign, createMachine } from "xstate"; -import type { Player } from '../@types/models'; +import type { Player } from "../@types/models"; const context = { - players: [] as Array, + players: [] as Player[], }; type Context = typeof context; type Events = - | { - player: Player; - type: 'playerJoins'; - } - | { - socketId: Player['socketId']; - type: 'playerLeaves'; - } - | { - type: 'playerClicksStart'; - }; + | { + player: Player; + type: "playerJoins"; + } + | { + socketId: Player["socketId"]; + type: "playerLeaves"; + } + | { + type: "playerClicksStart"; + }; -const isNewPlayer = ({ players }: { players: Array }, { player: playerFromEvent }: { player: Player }) => - players.find((player) => player.socketId === playerFromEvent.socketId) === undefined; +const isNewPlayer = ( + { players }: { players: Player[] }, + { player: playerFromEvent }: { player: Player }, +) => + players.find((player) => player.socketId === playerFromEvent.socketId) === + undefined; -const isOnlyPlayer = ({ players }: { players: Array }) => players.length === 1; +const isOnlyPlayer = ({ players }: { players: Player[] }) => + players.length === 1; const lobbyMachine = createMachine( - { - context: context, - id: 'lobby', - initial: 'Empty', - predictableActionArguments: true, - schema: { - context: {} as Context, - events: {} as Events, - }, - states: { - Empty: { - on: { playerJoins: { actions: 'addPlayer', target: 'OnePlayer' } }, - }, - MultiplePlayers: { - always: { - cond: 'isOnlyPlayer', - target: 'OnePlayer', - }, - on: { - playerJoins: { actions: 'addPlayer' }, - playerLeaves: { actions: 'removePlayer' }, - }, - }, - OnePlayer: { - on: { - playerJoins: { - actions: 'addPlayer', - cond: 'isNewPlayer', - target: 'MultiplePlayers', - }, - playerLeaves: { - actions: 'removePlayer', - target: 'Empty', - }, - }, - }, - }, - // eslint-disable-next-line @typescript-eslint/consistent-type-imports - tsTypes: {} as import('./lobby.typegen').Typegen0, - }, - { - actions: { - addPlayer: assign({ - players: ({ players }, { player }) => [...players, player], - }), - removePlayer: assign({ - players: ({ players }, { socketId }) => players.filter((p) => p.socketId !== socketId), - }), - }, - guards: { - isNewPlayer, - isOnlyPlayer, - }, - }, + { + context: context, + id: "lobby", + initial: "empty", + predictableActionArguments: true, + schema: { + context: {} as Context, + events: {} as Events, + }, + states: { + empty: { + on: { playerJoins: { actions: "addPlayer", target: "onePlayer" } }, + }, + multiplePlayers: { + always: { + cond: "isOnlyPlayer", + target: "onePlayer", + }, + on: { + playerJoins: { actions: "addPlayer" }, + playerLeaves: { actions: "removePlayer" }, + }, + }, + onePlayer: { + on: { + playerJoins: { + actions: "addPlayer", + cond: "isNewPlayer", + target: "multiplePlayers", + }, + playerLeaves: { + actions: "removePlayer", + target: "empty", + }, + }, + }, + }, + tsTypes: {} as import("./lobby.typegen").Typegen0, + }, + { + actions: { + addPlayer: assign({ + players: ({ players }, { player }) => [...players, player], + }), + removePlayer: assign({ + players: ({ players }, { socketId }) => + players.filter((p) => p.socketId !== socketId), + }), + }, + guards: { + isNewPlayer, + isOnlyPlayer, + }, + }, ); export { context, isNewPlayer, isOnlyPlayer, lobbyMachine }; diff --git a/server/machines/lobby.typegen.ts b/server/machines/lobby.typegen.ts index 931d5027..b6f78ad6 100644 --- a/server/machines/lobby.typegen.ts +++ b/server/machines/lobby.typegen.ts @@ -1,36 +1,28 @@ +// This file was automatically generated. Edits will be overwritten - // This file was automatically generated. Edits will be overwritten - - export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - "": { type: "" }; -"xstate.init": { type: "xstate.init" }; - }; - invokeSrcNameMap: { - - }; - missingImplementations: { - actions: never; - delays: never; - guards: never; - services: never; - }; - eventsCausingActions: { - "addPlayer": "playerJoins"; -"removePlayer": "playerLeaves"; - }; - eventsCausingDelays: { - - }; - eventsCausingGuards: { - "isNewPlayer": "playerJoins"; -"isOnlyPlayer": ""; - }; - eventsCausingServices: { - - }; - matchesStates: "Empty" | "MultiplePlayers" | "OnePlayer"; - tags: never; - } - \ No newline at end of file +export interface Typegen0 { + "@@xstate/typegen": true; + internalEvents: { + "": { type: "" }; + "xstate.init": { type: "xstate.init" }; + }; + invokeSrcNameMap: {}; + missingImplementations: { + actions: never; + delays: never; + guards: never; + services: never; + }; + eventsCausingActions: { + addPlayer: "playerJoins"; + removePlayer: "playerLeaves"; + }; + eventsCausingDelays: {}; + eventsCausingGuards: { + isNewPlayer: "playerJoins"; + isOnlyPlayer: ""; + }; + eventsCausingServices: {}; + matchesStates: "empty" | "multiplePlayers" | "onePlayer"; + tags: never; +} diff --git a/server/machines/round.ts b/server/machines/round.ts index e0a22756..f4345550 100644 --- a/server/machines/round.ts +++ b/server/machines/round.ts @@ -1,50 +1,51 @@ -import { assign, createMachine } from 'xstate'; +import { assign, createMachine } from "xstate"; type Question = { - answer: Array; - number: number; - question: string; + answer: string[]; + number: number; + question: string; }; const context = { - questions: [] as Array, - selectedQuestion: {} as Question | undefined, + questions: [] as Question[], + selectedQuestion: {} as Question | undefined, }; type Context = typeof context; type Events = { - type: string; + type: string; }; const gameMachine = createMachine( - { - context, - id: 'game', - initial: 'GameStart', - predictableActionArguments: true, - schema: { - context: {} as Context, - events: {} as Events, - }, - states: { - GameStart: { - entry: ['setQuestion'], - }, - }, - // eslint-disable-next-line @typescript-eslint/consistent-type-imports - tsTypes: {} as import('./round.typegen').Typegen0, - }, - { - actions: { - setQuestion: assign({ - selectedQuestion: ({ questions }) => { - const questionIndex = Math.floor(Math.random() * (questions.length - 1)); - return questions[questionIndex]; - }, - }), - }, - }, + { + context, + id: "game", + initial: "gameStart", + predictableActionArguments: true, + schema: { + context: {} as Context, + events: {} as Events, + }, + states: { + gameStart: { + entry: ["setQuestion"], + }, + }, + tsTypes: {} as import("./round.typegen").Typegen0, + }, + { + actions: { + setQuestion: assign({ + selectedQuestion: ({ questions }) => { + const questionIndex = Math.floor( + Math.random() * (questions.length - 1), + ); + return questions[questionIndex]; + }, + }), + }, + }, ); export { context, gameMachine }; diff --git a/server/machines/round.typegen.ts b/server/machines/round.typegen.ts index 1e542ae2..fae12631 100644 --- a/server/machines/round.typegen.ts +++ b/server/machines/round.typegen.ts @@ -1,33 +1,23 @@ +// This file was automatically generated. Edits will be overwritten - // This file was automatically generated. Edits will be overwritten - - export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - "xstate.init": { type: "xstate.init" }; - }; - invokeSrcNameMap: { - - }; - missingImplementations: { - actions: never; - delays: never; - guards: never; - services: never; - }; - eventsCausingActions: { - "setQuestion": "xstate.init"; - }; - eventsCausingDelays: { - - }; - eventsCausingGuards: { - - }; - eventsCausingServices: { - - }; - matchesStates: "GameStart"; - tags: never; - } - \ No newline at end of file +export interface Typegen0 { + "@@xstate/typegen": true; + internalEvents: { + "xstate.init": { type: "xstate.init" }; + }; + invokeSrcNameMap: {}; + missingImplementations: { + actions: never; + delays: never; + guards: never; + services: never; + }; + eventsCausingActions: { + setQuestion: "xstate.init"; + }; + eventsCausingDelays: {}; + eventsCausingGuards: {}; + eventsCausingServices: {}; + matchesStates: "gameStart"; + tags: never; +} diff --git a/server/round.ts b/server/round.ts index 90b77ade..7d4275be 100644 --- a/server/round.ts +++ b/server/round.ts @@ -1,20 +1,24 @@ -import type { InterpreterFrom } from 'xstate'; -import { interpret } from 'xstate'; +import type { InterpreterFrom } from "xstate"; +import { interpret } from "xstate"; -import questions from './data/questions.json'; -import { context, gameMachine } from './machines/round'; -import type { SocketServer } from './socketServer'; +import questions from "./data/questions.json"; +import { context, gameMachine } from "./machines/round"; +import type { SocketServer } from "./socketServer"; -export default class Round { - machine: InterpreterFrom; - server: SocketServer; +class Round { + machine: InterpreterFrom; + server: SocketServer; - constructor(server: SocketServer) { - this.server = server; - this.machine = interpret(gameMachine.withContext({ ...context, questions })).start(); + constructor(server: SocketServer) { + this.server = server; + this.machine = interpret( + gameMachine.withContext({ ...context, questions }), + ).start(); - this.machine.onTransition((state) => { - console.info({ context: state.context, state: state.value }); - }); - } + this.machine.onTransition((state) => { + console.info({ context: state.context, state: state.value }); + }); + } } + +export { Round }; diff --git a/server/socketServer.ts b/server/socketServer.ts index 3e997802..76e9f712 100644 --- a/server/socketServer.ts +++ b/server/socketServer.ts @@ -1,44 +1,48 @@ -import type { Server as HttpServer } from 'http'; -import { Server } from 'socket.io'; +import type { Server as HttpServer } from "http"; +import { Server } from "socket.io"; -import type { Question } from './@types/models'; -import ClientboundEvents from './events/clientbound'; -import ServerboundEvents from './events/severbound'; -import Lobby from './lobby'; -import Round from './round'; +import type { Question } from "./@types/models"; +import { clientboundEvents } from "./events/clientbound"; +import { serverboundEvents } from "./events/severbound"; +import { Lobby } from "./lobby"; +import { Round } from "./round"; export class SocketServer { - lobby: Lobby; - round?: Round; - server: Server; - - constructor(httpServer: HttpServer) { - this.lobby = new Lobby(this); - this.server = new Server(httpServer, {}); - - this.onCreated(); - } - - onCreated() { - this.server.on('connection', (socket) => { - console.info(`connected: ${socket.id}`); - - socket.emit(...ClientboundEvents.getPlayers(this.lobby)); - socket.on(...ServerboundEvents.postPlayers(this.lobby, socket, this.server)); - socket.on(...ServerboundEvents.disconnect(this.lobby, socket, this.server)); - socket.on(...ServerboundEvents.startRound(this)); - }); - } - - onQuestionSet(question: Question) { - this.server.emit(...ClientboundEvents.getQuestion(question)); - } - - onRoundStarted() { - this.round = new Round(this); - } - - onShowStartButton() { - this.server.emit(ClientboundEvents.showStartButton()); - } + lobby: Lobby; + round?: Round; + server: Server; + + constructor(httpServer: HttpServer) { + this.lobby = new Lobby(this); + this.server = new Server(httpServer, {}); + + this.onCreated(); + } + + onCreated() { + this.server.on("connection", (socket) => { + console.info(`connected: ${socket.id}`); + + socket.emit(...clientboundEvents.getPlayers(this.lobby)); + socket.on( + ...serverboundEvents.postPlayers(this.lobby, socket, this.server), + ); + socket.on( + ...serverboundEvents.disconnect(this.lobby, socket, this.server), + ); + socket.on(...serverboundEvents.startRound(this)); + }); + } + + onQuestionSet(question: Question) { + this.server.emit(...clientboundEvents.getQuestion(question)); + } + + onRoundStarted() { + this.round = new Round(this); + } + + onShowStartButton() { + this.server.emit(clientboundEvents.showStartButton()); + } } diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..c449f7e8 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1325 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 +# bun ./bun.lockb --hash: 907588F942064938-f01f6cb74a06d679-F22679F523A03F1B-48256c0f3cfd457d + + +"@babel/code-frame@^7.0.0": + version "7.23.5" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@biomejs/biome@^1.5.3": + version "1.5.3" + resolved "https://registry.npmjs.org/@biomejs/biome/-/biome-1.5.3.tgz" + integrity sha512-yvZCa/g3akwTaAQ7PCwPWDCkZs3Qa5ONg/fgOUT9e6wAWsPftCjLQFPXBeGxPK30yZSSpgEmRCfpGTmVbUjGgg== + optionalDependencies: + "@biomejs/cli-win32-x64" "1.5.3" + "@biomejs/cli-win32-arm64" "1.5.3" + "@biomejs/cli-darwin-x64" "1.5.3" + "@biomejs/cli-darwin-arm64" "1.5.3" + "@biomejs/cli-linux-x64" "1.5.3" + "@biomejs/cli-linux-arm64" "1.5.3" + "@biomejs/cli-linux-x64-musl" "1.5.3" + "@biomejs/cli-linux-arm64-musl" "1.5.3" + +"@biomejs/cli-darwin-arm64@1.5.3": + version "1.5.3" + resolved "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.5.3.tgz" + integrity sha512-ImU7mh1HghEDyqNmxEZBoMPr8SxekkZuYcs+gynKlNW+TALQs7swkERiBLkG9NR0K1B3/2uVzlvYowXrmlW8hw== + +"@biomejs/cli-darwin-x64@1.5.3": + version "1.5.3" + resolved "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.5.3.tgz" + integrity sha512-vCdASqYnlpq/swErH7FD6nrFz0czFtK4k/iLgj0/+VmZVjineFPgevOb+Sr9vz0tk0GfdQO60bSpI74zU8M9Dw== + +"@biomejs/cli-linux-arm64@1.5.3": + version "1.5.3" + resolved "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.5.3.tgz" + integrity sha512-cupBQv0sNF1OKqBfx7EDWMSsKwRrBUZfjXawT4s6hKV6ALq7p0QzWlxr/sDmbKMLOaLQtw2Qgu/77N9rm+f9Rg== + +"@biomejs/cli-linux-arm64-musl@1.5.3": + version "1.5.3" + resolved "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.3.tgz" + integrity sha512-DYuMizUYUBYfS0IHGjDrOP1RGipqWfMGEvNEJ398zdtmCKLXaUvTimiox5dvx4X15mBK5M2m8wgWUgOP1giUpQ== + +"@biomejs/cli-linux-x64@1.5.3": + version "1.5.3" + resolved "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.5.3.tgz" + integrity sha512-YQrSArQvcv4FYsk7Q91Yv4uuu5F8hJyORVcv3zsjCLGkjIjx2RhjYLpTL733SNL7v33GmOlZY0eFR1ko38tuUw== + +"@biomejs/cli-linux-x64-musl@1.5.3": + version "1.5.3" + resolved "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.3.tgz" + integrity sha512-UUHiAnlDqr2Y/LpvshBFhUYMWkl2/Jn+bi3U6jKuav0qWbbBKU/ByHgR4+NBxpKBYoCtWxhnmatfH1bpPIuZMw== + +"@biomejs/cli-win32-arm64@1.5.3": + version "1.5.3" + resolved "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.5.3.tgz" + integrity sha512-HxatYH7vf/kX9nrD+pDYuV2GI9GV8EFo6cfKkahAecTuZLPxryHx1WEfJthp5eNsE0+09STGkKIKjirP0ufaZA== + +"@biomejs/cli-win32-x64@1.5.3": + version "1.5.3" + resolved "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.5.3.tgz" + integrity sha512-fMvbSouZEASU7mZH8SIJSANDm5OqsjgtVXlbUqxwed6BP7uuHRSs396Aqwh2+VoW8fwTpp6ybIUoC9FrzB0kyA== + +"@csstools/css-parser-algorithms@^2.5.0": + version "2.5.0" + resolved "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz" + integrity sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ== + +"@csstools/css-tokenizer@^2.2.3": + version "2.2.3" + resolved "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz" + integrity sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg== + +"@csstools/media-query-list-parser@^2.1.7": + version "2.1.7" + resolved "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.7.tgz" + integrity sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ== + +"@csstools/selector-specificity@^3.0.1": + version "3.0.1" + resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz" + integrity sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@playwright/test@^1.39.0": + version "1.41.2" + resolved "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz" + integrity sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg== + dependencies: + playwright "1.41.2" + +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + +"@types/cors@^2.8.12": + version "2.8.17" + resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@>=10.0.0", "@types/node@~20.11.3": + version "20.11.17" + resolved "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz" + integrity sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw== + dependencies: + undici-types "~5.26.4" + +"@types/serve-handler@^6.1.1": + version "6.1.4" + resolved "https://registry.npmjs.org/@types/serve-handler/-/serve-handler-6.1.4.tgz" + integrity sha512-aXy58tNie0NkuSCY291xUxl0X+kGYy986l4kqW6Gi4kEXgr6Tx0fpSH7YwUSa5usPpG3s9DBeIR6hHcDtL2IvQ== + dependencies: + "@types/node" "*" + +"@types/web@^0.0.138": + version "0.0.138" + resolved "https://registry.npmjs.org/@types/web/-/web-0.0.138.tgz" + integrity sha512-oQD74hl+cNCZdSWIupJCXZ2azTuB3MJ/mrWlgYt+v4pD7/Dr78gl5hKAdieZNf9NrAqwUez79bHtnFVSNSscWA== + +"@types/ws@~8.5.10": + version "8.5.10" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + +accepts@~1.3.4: + version "1.3.8" + resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +ajv@^8.0.1: + version "8.12.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +balanced-match@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz" + integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== + +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +bun-types@^1.0.2: + version "1.0.26" + resolved "https://registry.npmjs.org/bun-types/-/bun-types-1.0.26.tgz" + integrity sha512-VcSj+SCaWIcMb0uSGIAtr8P92zq9q+unavcQmx27fk6HulCthXHBVrdGuXxAZbFtv7bHVjizRzR2mk9r/U8Nkg== + dependencies: + "@types/node" "~20.11.3" + "@types/ws" "~8.5.10" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colord@^2.9.3: + version "2.9.3" + resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz" + integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== + +cookie@~0.4.1: + version "0.4.2" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +cors@~2.8.5: + version "2.8.5" + resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz" + integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + dependencies: + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-functions-list@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.1.tgz" + integrity sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ== + +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +engine.io@~6.5.2: + version "6.5.4" + resolved "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz" + integrity sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg== + dependencies: + "@types/cookie" "^0.4.1" + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.4.1" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + +engine.io-client@~6.5.2: + version "6.5.3" + resolved "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz" + integrity sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + xmlhttprequest-ssl "~2.0.0" + +engine.io-parser@~5.2.1: + version "5.2.2" + resolved "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz" + integrity sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw== + +env-paths@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9, fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-url-parser@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz" + integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== + dependencies: + punycode "^1.3.2" + +fastest-levenshtein@^1.0.16: + version "1.0.16" + resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +flat-cache@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.0.tgz" + integrity sha512-EryKbCE/wxpxKniQlyas6PY1I9vwtF3uCBweX+N8KYTCn3Y12RTGtQAJ/bd5pl7kxUAc8v/R3Ake/N17OZiFqA== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + rimraf "^5.0.5" + +flatted@^3.2.9: + version "3.2.9" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +glob@^10.3.7: + version "10.3.10" + resolved "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +globjoin@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz" + integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + +ignore@^5.2.0, ignore@^5.3.0: + version "5.3.1" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +ini@^1.3.5: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +known-css-properties@^0.29.0: + version "0.29.0" + resolved "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz" + integrity sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + +"lru-cache@^9.1.1 || ^10.0.0": + version "10.2.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz" + integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== + +mathml-tag-names@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz" + integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + +meow@^13.1.0: + version "13.2.0" + resolved "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz" + integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + +mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.4" + resolved "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-assign@^4: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-is-inside@1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz" + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz" + integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +playwright@1.41.2: + version "1.41.2" + resolved "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz" + integrity sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A== + dependencies: + playwright-core "1.41.2" + optionalDependencies: + fsevents "2.3.2" + +playwright-core@1.41.2: + version "1.41.2" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz" + integrity sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA== + +postcss@^8.4.20, postcss@^8.4.31, postcss@^8.4.32, postcss@^8.4.33: + version "8.4.35" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz" + integrity sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss-resolve-nested-selector@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz" + integrity sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw== + +postcss-safe-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz" + integrity sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg== + +postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.15: + version "6.0.15" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz" + integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-sorting@^8.0.2: + version "8.0.2" + resolved "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz" + integrity sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q== + +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +punycode@^1.3.2: + version "1.4.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" + integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^5.0.5: + version "5.0.5" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz" + integrity sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A== + dependencies: + glob "^10.3.7" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +serve-handler@^6.1.3: + version "6.1.5" + resolved "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz" + integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + fast-url-parser "1.1.3" + mime-types "2.1.18" + minimatch "3.1.2" + path-is-inside "1.0.2" + path-to-regexp "2.2.1" + range-parser "1.2.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +socket.io@^4.1.3: + version "4.7.4" + resolved "https://registry.npmjs.org/socket.io/-/socket.io-4.7.4.tgz" + integrity sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.3.2" + engine.io "~6.5.2" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + +socket.io-adapter@~2.5.2: + version "2.5.2" + resolved "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz" + integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== + dependencies: + ws "~8.11.0" + +socket.io-client@^4.7.2: + version "4.7.4" + resolved "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz" + integrity sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.5.2" + socket.io-parser "~4.2.4" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + +source-map-js@^1.0.1, source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +string-width@^4.1.0, string-width@^4.2.3, "string-width@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1, strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +stylelint@>=14, "stylelint@^14.0.0 || ^15.0.0 || ^16.0.1", stylelint@^16.0.0, stylelint@^16.1.0: + version "16.2.1" + resolved "https://registry.npmjs.org/stylelint/-/stylelint-16.2.1.tgz" + integrity sha512-SfIMGFK+4n7XVAyv50CpVfcGYWG4v41y6xG7PqOgQSY8M/PgdK0SQbjWFblxjJZlN9jNq879mB4BCZHJRIJ1hA== + dependencies: + "@csstools/css-parser-algorithms" "^2.5.0" + "@csstools/css-tokenizer" "^2.2.3" + "@csstools/media-query-list-parser" "^2.1.7" + "@csstools/selector-specificity" "^3.0.1" + balanced-match "^2.0.0" + colord "^2.9.3" + cosmiconfig "^9.0.0" + css-functions-list "^3.2.1" + css-tree "^2.3.1" + debug "^4.3.4" + fast-glob "^3.3.2" + fastest-levenshtein "^1.0.16" + file-entry-cache "^8.0.0" + global-modules "^2.0.0" + globby "^11.1.0" + globjoin "^0.1.4" + html-tags "^3.3.1" + ignore "^5.3.0" + imurmurhash "^0.1.4" + is-plain-object "^5.0.0" + known-css-properties "^0.29.0" + mathml-tag-names "^2.1.3" + meow "^13.1.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.33" + postcss-resolve-nested-selector "^0.1.1" + postcss-safe-parser "^7.0.0" + postcss-selector-parser "^6.0.15" + postcss-value-parser "^4.2.0" + resolve-from "^5.0.0" + string-width "^4.2.3" + strip-ansi "^7.1.0" + supports-hyperlinks "^3.0.0" + svg-tags "^1.0.0" + table "^6.8.1" + write-file-atomic "^5.0.1" + +stylelint-config-clean-order@^5.2.0: + version "5.4.1" + resolved "https://registry.npmjs.org/stylelint-config-clean-order/-/stylelint-config-clean-order-5.4.1.tgz" + integrity sha512-C9E94vFk7QKqPshXik3iNU5cYz7vm0Up4/wu1biRjThWLWJ3gYRdXfyV/1fFU7u4ThfSIf2/ijNhk0pf0ErPWQ== + dependencies: + stylelint-order "^6.0.4" + +stylelint-config-recommended@^14.0.0: + version "14.0.0" + resolved "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.0.tgz" + integrity sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ== + +stylelint-config-standard@^36.0.0: + version "36.0.0" + resolved "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-36.0.0.tgz" + integrity sha512-3Kjyq4d62bYFp/Aq8PMKDwlgUyPU4nacXsjDLWJdNPRUgpuxALu1KnlAHIj36cdtxViVhXexZij65yM0uNIHug== + dependencies: + stylelint-config-recommended "^14.0.0" + +stylelint-order@^6.0.4: + version "6.0.4" + resolved "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz" + integrity sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA== + dependencies: + postcss "^8.4.32" + postcss-sorting "^8.0.2" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz" + integrity sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz" + integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== + +table@^6.8.1: + version "6.8.1" + resolved "https://registry.npmjs.org/table/-/table-6.8.1.tgz" + integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +typescript@>=4.9.5, typescript@^5.2.2: + version "5.3.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +vary@^1: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +which@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +"wrap-ansi@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + +xstate@4.38.3: + version "4.38.3" + resolved "https://registry.npmjs.org/xstate/-/xstate-4.38.3.tgz" + integrity sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw==