diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..4cf897b --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +/.expo +node_modules \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..fe5b366 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,8 @@ +// https://docs.expo.dev/guides/using-eslint/ +module.exports = { + extends: ["expo", "prettier"], + plugins: ["prettier"], + rules: { + "prettier/prettier": "error", + }, +}; diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..46773ab --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,35 @@ +name: CI +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + lint-and-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Find yarn cache location + id: yarn-cache + run: echo "::set-output name=dir::$(yarn cache dir)" + + - name: JS package cache + uses: actions/cache@v1 + with: + path: $(( steps.yarn-cache.outputs.dir )) + key: $(( runner.os ))-yarn-$(( hashFiles('**/yarn.lock') )) + restore-keys: | + $(( runner.os ))-yarn- + + - name: Install Node Modules + run: yarn install + + - name: Run Lint + run: yarn lint + + - name: Run tests + run: yarn testFinal diff --git a/__tests__/App.test.tsx b/__tests__/App.test.tsx index 4b7e7b1..740a1ae 100644 --- a/__tests__/App.test.tsx +++ b/__tests__/App.test.tsx @@ -1,14 +1,14 @@ -import renderer from "react-test-renderer"; -import App from "../App"; - -describe("", () => { - it("has 1 child", () => { - const tree: any = renderer.create().toJSON(); - expect(tree.children.length).toBe(1); - }); - - it("renders correctly", () => { - const tree = renderer.create().toJSON(); - expect(tree).toMatchSnapshot(); - }); -}); +import renderer from "react-test-renderer"; +import App from "../App"; + +describe("", () => { + it("has 1 child", () => { + const tree: any = renderer.create().toJSON(); + expect(tree.children.length).toBe(1); + }); + + it("renders correctly", () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/__tests__/__mocks__/boardData.mock.ts b/__tests__/__mocks__/boardData.mock.ts index f2b5733..1ac0400 100644 --- a/__tests__/__mocks__/boardData.mock.ts +++ b/__tests__/__mocks__/boardData.mock.ts @@ -1,6 +1,6 @@ -import { BOARD_SIZES } from "../../src/constants/common"; -import { createBoard } from "../../src/utils/common"; - -const board = createBoard(BOARD_SIZES._3x3); - -export default board; +import { BOARD_SIZES } from "../../src/constants/common"; +import { createBoard } from "../../src/utils/common"; + +const board = createBoard(BOARD_SIZES._3x3); + +export default board; diff --git a/__tests__/components/Board.test.tsx b/__tests__/components/Board.test.tsx index dc51d64..d08ba01 100644 --- a/__tests__/components/Board.test.tsx +++ b/__tests__/components/Board.test.tsx @@ -1,94 +1,94 @@ -import React from "react"; -import renderer from "react-test-renderer"; -import Board from "../../src/components/Board"; -import board from "../__mocks__/boardData.mock"; -import { render, screen, userEvent } from "@testing-library/react-native"; -import { GAME_STATES, PLAYER_O, PLAYER_X } from "../../src/constants/common"; - -const onPressCallbackGlobal = jest.fn(); - -const mockProps = { - board: board, - gameState: GAME_STATES.inProgress, - disabled: false, - onPressUser: onPressCallbackGlobal, -}; - -describe("", () => { - const BoardComponent = () => ; - - test("renders correctly", () => { - const tree = renderer.create().toJSON(); - expect(tree).toMatchSnapshot(); - }); - - test("return the correct row and col indices when first square pressed", async () => { - render(); - - const firstSquare = screen.getByTestId("00"); - - expect(firstSquare).toBeDefined(); - - await userEvent.press(firstSquare); - - //enure the correct row and column indices are correct - expect(onPressCallbackGlobal).toHaveBeenCalledWith(0, 0); - }); - - test("prevent pressing when board is disabled", async () => { - const onPressCallback = jest.fn(); - render( - - ); - - const firstSquare = screen.getByTestId("00"); - - expect(firstSquare).toBeDefined(); - - await userEvent.press(firstSquare); - - //enure callBack did not fire - expect(onPressCallback).not.toHaveBeenCalled(); - }); - - test("should display X and O for PLAYER_X and PLAYER_O", async () => { - const boardCopy = board.map((arr) => arr.slice()); - boardCopy[0][0] = PLAYER_X; - boardCopy[0][1] = PLAYER_O; - const onPressCallback = jest.fn(); - - render( - - ); - - //Player X should exist on the board - expect(screen.getByText("X")).toBeDefined(); - - //Player O should exist on the board - expect(screen.getByText("O")).toBeDefined(); - - const squareX = screen.getByTestId("00"); - const squareO = screen.getByTestId("01"); - - expect(squareX).toBeDefined(); - expect(squareO).toBeDefined(); - }); - - test("should disable board when game is ended", async () => { - const onPressCallback = jest.fn(); - render( - - ); - - const square = screen.getByTestId("00"); - - await userEvent.press(square); - - //enure board is disabled - expect(onPressCallback).not.toHaveBeenCalled(); - }); -}); +import React from "react"; +import renderer from "react-test-renderer"; +import Board from "../../src/components/Board"; +import board from "../__mocks__/boardData.mock"; +import { render, screen, userEvent } from "@testing-library/react-native"; +import { GAME_STATES, PLAYER_O, PLAYER_X } from "../../src/constants/common"; + +const onPressCallbackGlobal = jest.fn(); + +const mockProps = { + board: board, + gameState: GAME_STATES.inProgress, + disabled: false, + onPressUser: onPressCallbackGlobal, +}; + +describe("", () => { + const BoardComponent = () => ; + + test("renders correctly", () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + + test("return the correct row and col indices when first square pressed", async () => { + render(); + + const firstSquare = screen.getByTestId("00"); + + expect(firstSquare).toBeDefined(); + + await userEvent.press(firstSquare); + + //enure the correct row and column indices are correct + expect(onPressCallbackGlobal).toHaveBeenCalledWith(0, 0); + }); + + test("prevent pressing when board is disabled", async () => { + const onPressCallback = jest.fn(); + render( + , + ); + + const firstSquare = screen.getByTestId("00"); + + expect(firstSquare).toBeDefined(); + + await userEvent.press(firstSquare); + + //enure callBack did not fire + expect(onPressCallback).not.toHaveBeenCalled(); + }); + + test("should display X and O for PLAYER_X and PLAYER_O", async () => { + const boardCopy = board.map((arr) => arr.slice()); + boardCopy[0][0] = PLAYER_X; + boardCopy[0][1] = PLAYER_O; + const onPressCallback = jest.fn(); + + render( + , + ); + + //Player X should exist on the board + expect(screen.getByText("X")).toBeDefined(); + + //Player O should exist on the board + expect(screen.getByText("O")).toBeDefined(); + + const squareX = screen.getByTestId("00"); + const squareO = screen.getByTestId("01"); + + expect(squareX).toBeDefined(); + expect(squareO).toBeDefined(); + }); + + test("should disable board when game is ended", async () => { + const onPressCallback = jest.fn(); + render( + , + ); + + const square = screen.getByTestId("00"); + + await userEvent.press(square); + + //enure board is disabled + expect(onPressCallback).not.toHaveBeenCalled(); + }); +}); diff --git a/__tests__/components/Game.test.tsx b/__tests__/components/Game.test.tsx index 054362b..a613aea 100644 --- a/__tests__/components/Game.test.tsx +++ b/__tests__/components/Game.test.tsx @@ -1,191 +1,191 @@ -import renderer from "react-test-renderer"; -import Game from "../../src/components/Game"; -import { render, screen, userEvent } from "@testing-library/react-native"; - -import * as utils from "../../src/utils/common"; - -const mockGetRandomMove = jest.spyOn(utils, "getRandomMove"); - -describe("", () => { - test("renders correctly", () => { - const tree = renderer.create().toJSON(); - expect(tree).toMatchSnapshot(); - }); - - test("should display the correct labels when game is started", async () => { - render(); - - const startGameBtn = screen.getByTestId("startGameBtnTestId"); - - expect(startGameBtn).toBeDefined(); - - await userEvent.press(startGameBtn); - - expect(screen.getByText("USER .vs COMPUTER")).toBeDefined(); - expect(screen.getByText("Game in progress")).toBeDefined(); - expect(screen.getByText("Player X's turn")).toBeDefined(); - }); - - test("should player X (human) make the first move by default, and then player O (computer)", async () => { - jest.useFakeTimers(); - - render(); - - const startGameBtn = screen.getByTestId("startGameBtnTestId"); - - await userEvent.press(startGameBtn); - - const square = screen.getByTestId("00"); - - await userEvent.press(square); - - //Player X should exist on the board - expect(screen.getByText("X")).toBeDefined(); - - expect(screen.getByText(`Player O's turn`)).toBeDefined(); - - jest.runAllTimers(); - - //Player O should exist now after making the next move - expect(screen.getByText("O")).toBeDefined(); - }); - - test("should simulate a winning game for the human .vs computer", async () => { - jest.useFakeTimers(); - - render(); - - const startGameBtn = screen.getByTestId("startGameBtnTestId"); - - await userEvent.press(startGameBtn); - - //we want human to win using these combinations - const square1 = screen.getByTestId("00"); - const square2 = screen.getByTestId("01"); - const square3 = screen.getByTestId("02"); - - // human's move - await userEvent.press(square1); - - // computer's move - mockGetRandomMove.mockReturnValue([1, 0]); - - jest.runAllTimers(); - - // human's move - await userEvent.press(square2); - - // computer's move - mockGetRandomMove.mockReturnValue([2, 1]); - - jest.runAllTimers(); - - // human's move - // at this point, human should win - await userEvent.press(square3); - - // computer's move - mockGetRandomMove.mockReturnValue([1, 2]); - - jest.runAllTimers(); - - expect(screen.getByText("Game over")).toBeDefined(); - expect(screen.getByText("The winner is player X")).toBeDefined(); - }); - - test("should simulate a tie game between human and computer", async () => { - jest.useFakeTimers(); - - render(); - - const startGameBtn = screen.getByTestId("startGameBtnTestId"); - - await userEvent.press(startGameBtn); - - //combinations for player X - const square1 = screen.getByTestId("01"); - const square2 = screen.getByTestId("02"); - const square3 = screen.getByTestId("10"); - const square4 = screen.getByTestId("21"); - const square5 = screen.getByTestId("22"); - - // human's move - await userEvent.press(square1); - - // computer's move - mockGetRandomMove.mockReturnValue([0, 0]); - - jest.runAllTimers(); - - // human's move - await userEvent.press(square2); - - // computer's move - mockGetRandomMove.mockReturnValue([1, 2]); - - jest.runAllTimers(); - - // human's move - await userEvent.press(square3); - - // computer's move - mockGetRandomMove.mockReturnValue([1, 1]); - - jest.runAllTimers(); - - // human's move - await userEvent.press(square4); - - // computer's move - mockGetRandomMove.mockReturnValue([2, 0]); - - jest.runAllTimers(); - - // human's move - await userEvent.press(square5); - - expect(screen.getByText("Game over")).toBeDefined(); - expect(screen.getByText("It's a tie")).toBeDefined(); - }); - - test("should simulate a battle between AI and human", async () => { - jest.useFakeTimers(); - - render(); - - const aiCheckbox = screen.getByTestId("aiCheckboxTestId"); - - await userEvent.press(aiCheckbox); - - const startGameBtn = screen.getByTestId("startGameBtnTestId"); - - await userEvent.press(startGameBtn); - - //combinations for player X (human) - const square1 = screen.getByTestId("00"); - const square2 = screen.getByTestId("02"); - const square3 = screen.getByTestId("10"); - - // human's move - await userEvent.press(square1); - - // AT's move - jest.runAllTimers(); - - // human's move - await userEvent.press(square2); - - // AI's move - jest.runAllTimers(); - - // human's move - await userEvent.press(square3); - - // AI's move - jest.runAllTimers(); - - // AI should win since the human's combinations above are easy to beat - expect(screen.getByText("Game over")).toBeDefined(); - expect(screen.getByText("The winner is player O")).toBeDefined(); - }); -}); +import renderer from "react-test-renderer"; +import Game from "../../src/components/Game"; +import { render, screen, userEvent } from "@testing-library/react-native"; + +import * as utils from "../../src/utils/common"; + +const mockGetRandomMove = jest.spyOn(utils, "getRandomMove"); + +describe("", () => { + test("renders correctly", () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + + test("should display the correct labels when game is started", async () => { + render(); + + const startGameBtn = screen.getByTestId("startGameBtnTestId"); + + expect(startGameBtn).toBeDefined(); + + await userEvent.press(startGameBtn); + + expect(screen.getByText("USER .vs COMPUTER")).toBeDefined(); + expect(screen.getByText("Game in progress")).toBeDefined(); + expect(screen.getByText("Player X's turn")).toBeDefined(); + }); + + test("should player X (human) make the first move by default, and then player O (computer)", async () => { + jest.useFakeTimers(); + + render(); + + const startGameBtn = screen.getByTestId("startGameBtnTestId"); + + await userEvent.press(startGameBtn); + + const square = screen.getByTestId("00"); + + await userEvent.press(square); + + //Player X should exist on the board + expect(screen.getByText("X")).toBeDefined(); + + expect(screen.getByText(`Player O's turn`)).toBeDefined(); + + jest.runAllTimers(); + + //Player O should exist now after making the next move + expect(screen.getByText("O")).toBeDefined(); + }); + + test("should simulate a winning game for the human .vs computer", async () => { + jest.useFakeTimers(); + + render(); + + const startGameBtn = screen.getByTestId("startGameBtnTestId"); + + await userEvent.press(startGameBtn); + + //we want human to win using these combinations + const square1 = screen.getByTestId("00"); + const square2 = screen.getByTestId("01"); + const square3 = screen.getByTestId("02"); + + // human's move + await userEvent.press(square1); + + // computer's move + mockGetRandomMove.mockReturnValue([1, 0]); + + jest.runAllTimers(); + + // human's move + await userEvent.press(square2); + + // computer's move + mockGetRandomMove.mockReturnValue([2, 1]); + + jest.runAllTimers(); + + // human's move + // at this point, human should win + await userEvent.press(square3); + + // computer's move + mockGetRandomMove.mockReturnValue([1, 2]); + + jest.runAllTimers(); + + expect(screen.getByText("Game over")).toBeDefined(); + expect(screen.getByText("The winner is player X")).toBeDefined(); + }); + + test("should simulate a tie game between human and computer", async () => { + jest.useFakeTimers(); + + render(); + + const startGameBtn = screen.getByTestId("startGameBtnTestId"); + + await userEvent.press(startGameBtn); + + //combinations for player X + const square1 = screen.getByTestId("01"); + const square2 = screen.getByTestId("02"); + const square3 = screen.getByTestId("10"); + const square4 = screen.getByTestId("21"); + const square5 = screen.getByTestId("22"); + + // human's move + await userEvent.press(square1); + + // computer's move + mockGetRandomMove.mockReturnValue([0, 0]); + + jest.runAllTimers(); + + // human's move + await userEvent.press(square2); + + // computer's move + mockGetRandomMove.mockReturnValue([1, 2]); + + jest.runAllTimers(); + + // human's move + await userEvent.press(square3); + + // computer's move + mockGetRandomMove.mockReturnValue([1, 1]); + + jest.runAllTimers(); + + // human's move + await userEvent.press(square4); + + // computer's move + mockGetRandomMove.mockReturnValue([2, 0]); + + jest.runAllTimers(); + + // human's move + await userEvent.press(square5); + + expect(screen.getByText("Game over")).toBeDefined(); + expect(screen.getByText("It's a tie")).toBeDefined(); + }); + + test("should simulate a battle between AI and human", async () => { + jest.useFakeTimers(); + + render(); + + const aiCheckbox = screen.getByTestId("aiCheckboxTestId"); + + await userEvent.press(aiCheckbox); + + const startGameBtn = screen.getByTestId("startGameBtnTestId"); + + await userEvent.press(startGameBtn); + + //combinations for player X (human) + const square1 = screen.getByTestId("00"); + const square2 = screen.getByTestId("02"); + const square3 = screen.getByTestId("10"); + + // human's move + await userEvent.press(square1); + + // AT's move + jest.runAllTimers(); + + // human's move + await userEvent.press(square2); + + // AI's move + jest.runAllTimers(); + + // human's move + await userEvent.press(square3); + + // AI's move + jest.runAllTimers(); + + // AI should win since the human's combinations above are easy to beat + expect(screen.getByText("Game over")).toBeDefined(); + expect(screen.getByText("The winner is player O")).toBeDefined(); + }); +}); diff --git a/__tests__/components/Settings.test.tsx b/__tests__/components/Settings.test.tsx index d0151de..e6ca4fa 100644 --- a/__tests__/components/Settings.test.tsx +++ b/__tests__/components/Settings.test.tsx @@ -1,95 +1,95 @@ -import React from "react"; -import renderer from "react-test-renderer"; -import { render, screen, userEvent } from "@testing-library/react-native"; -import { BOARD_SIZES } from "../../src/constants/common"; -import Settings from "../../src/components/Settings"; - -const settingsMock = { - playerX: "user", - playerO: "computer", - useAi: false, - boardSize: BOARD_SIZES._3x3, -}; - -describe("", () => { - test("renders correctly", () => { - const onStartGameMock = jest.fn(); - const tree = renderer - .create() - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - test("should emit the correct settings", async () => { - const onStartGameMock = jest.fn(); - render(); - - const board3x3 = screen.getByTestId("3x3TestId"); - const board4x4 = screen.getByTestId("4x4TestId"); - const board5x5 = screen.getByTestId("5x5TestId"); - - expect(board3x3).toBeDefined(); - - const startGameBtn = screen.getByTestId("startGameBtnTestId"); - - await userEvent.press(startGameBtn); - - //default settings - expect(onStartGameMock).toHaveBeenCalledWith(settingsMock); - - //select board 4x4 - await userEvent.press(board4x4); - await userEvent.press(startGameBtn); - - expect(onStartGameMock).toHaveBeenCalledWith({ - ...settingsMock, - boardSize: BOARD_SIZES._4x4, - }); - - //select board 5x5 - await userEvent.press(board5x5); - await userEvent.press(startGameBtn); - - expect(onStartGameMock).toHaveBeenCalledWith({ - ...settingsMock, - boardSize: BOARD_SIZES._5x5, - }); - }); - - test("should check the AI checkbox", async () => { - const onStartGameMock = jest.fn(); - render(); - - const aiCheckbox = screen.getByTestId("aiCheckboxTestId"); - - await userEvent.press(aiCheckbox); - - const startGameBtn = screen.getByTestId("startGameBtnTestId"); - - await userEvent.press(startGameBtn); - - expect(onStartGameMock).toHaveBeenCalledWith({ - ...settingsMock, - useAi: true, - }); - }); - - //FIXME: needs fixed! - xtest("should select the right player from dropdown", async () => { - const onStartGameMock = jest.fn(); - render(); - - const computerPlayer = screen.getByText("User"); - - await userEvent.press(computerPlayer); - - const startGameBtn = screen.getByTestId("startGameBtnTestId"); - - await userEvent.press(startGameBtn); - - expect(onStartGameMock).toHaveBeenCalledWith({ - ...settingsMock, - playerX: "computer", - }); - }); -}); +import React from "react"; +import renderer from "react-test-renderer"; +import { render, screen, userEvent } from "@testing-library/react-native"; +import { BOARD_SIZES } from "../../src/constants/common"; +import Settings from "../../src/components/Settings"; + +const settingsMock = { + playerX: "user", + playerO: "computer", + useAi: false, + boardSize: BOARD_SIZES._3x3, +}; + +describe("", () => { + test("renders correctly", () => { + const onStartGameMock = jest.fn(); + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + + test("should emit the correct settings", async () => { + const onStartGameMock = jest.fn(); + render(); + + const board3x3 = screen.getByTestId("3x3TestId"); + const board4x4 = screen.getByTestId("4x4TestId"); + const board5x5 = screen.getByTestId("5x5TestId"); + + expect(board3x3).toBeDefined(); + + const startGameBtn = screen.getByTestId("startGameBtnTestId"); + + await userEvent.press(startGameBtn); + + //default settings + expect(onStartGameMock).toHaveBeenCalledWith(settingsMock); + + //select board 4x4 + await userEvent.press(board4x4); + await userEvent.press(startGameBtn); + + expect(onStartGameMock).toHaveBeenCalledWith({ + ...settingsMock, + boardSize: BOARD_SIZES._4x4, + }); + + //select board 5x5 + await userEvent.press(board5x5); + await userEvent.press(startGameBtn); + + expect(onStartGameMock).toHaveBeenCalledWith({ + ...settingsMock, + boardSize: BOARD_SIZES._5x5, + }); + }); + + test("should check the AI checkbox", async () => { + const onStartGameMock = jest.fn(); + render(); + + const aiCheckbox = screen.getByTestId("aiCheckboxTestId"); + + await userEvent.press(aiCheckbox); + + const startGameBtn = screen.getByTestId("startGameBtnTestId"); + + await userEvent.press(startGameBtn); + + expect(onStartGameMock).toHaveBeenCalledWith({ + ...settingsMock, + useAi: true, + }); + }); + + //FIXME: needs fixed! + xtest("should select the right player from dropdown", async () => { + const onStartGameMock = jest.fn(); + render(); + + const computerPlayer = screen.getByText("User"); + + await userEvent.press(computerPlayer); + + const startGameBtn = screen.getByTestId("startGameBtnTestId"); + + await userEvent.press(startGameBtn); + + expect(onStartGameMock).toHaveBeenCalledWith({ + ...settingsMock, + playerX: "computer", + }); + }); +}); diff --git a/__tests__/components/Square.test.tsx b/__tests__/components/Square.test.tsx index e323b0e..1aa78d3 100644 --- a/__tests__/components/Square.test.tsx +++ b/__tests__/components/Square.test.tsx @@ -1,48 +1,48 @@ -import React from "react"; -import renderer from "react-test-renderer"; -import { render, screen, userEvent } from "@testing-library/react-native"; -import Square from "../../src/components/Square"; - -const onSquarePressMock = jest.fn(); -const propsMock = { - value: "X", - disabled: false, - testID: "squareTestId", - onSquarePress: onSquarePressMock, -}; - -describe("", () => { - test("renders correctly", () => { - const tree = renderer.create().toJSON(); - expect(tree).toMatchSnapshot(); - }); - - test("should display the correct value, e.g. X", async () => { - render(); - - const x = screen.getByText("X"); - - expect(x).toBeDefined(); - }); - - test("should fire onSquarePress when square is pressed", async () => { - render(); - - const x = screen.getByText("X"); - - await userEvent.press(x); - - expect(onSquarePressMock).toHaveBeenCalled(); - }); - - test("should fire not onSquarePress when square is disabled", async () => { - const callBack = jest.fn(); - render(); - - const x = screen.getByText("X"); - - await userEvent.press(x); - - expect(callBack).not.toHaveBeenCalled(); - }); -}); +import React from "react"; +import renderer from "react-test-renderer"; +import { render, screen, userEvent } from "@testing-library/react-native"; +import Square from "../../src/components/Square"; + +const onSquarePressMock = jest.fn(); +const propsMock = { + value: "X", + disabled: false, + testID: "squareTestId", + onSquarePress: onSquarePressMock, +}; + +describe("", () => { + test("renders correctly", () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + + test("should display the correct value, e.g. X", async () => { + render(); + + const x = screen.getByText("X"); + + expect(x).toBeDefined(); + }); + + test("should fire onSquarePress when square is pressed", async () => { + render(); + + const x = screen.getByText("X"); + + await userEvent.press(x); + + expect(onSquarePressMock).toHaveBeenCalled(); + }); + + test("should fire not onSquarePress when square is disabled", async () => { + const callBack = jest.fn(); + render(); + + const x = screen.getByText("X"); + + await userEvent.press(x); + + expect(callBack).not.toHaveBeenCalled(); + }); +}); diff --git a/babel.config.js b/babel.config.js index 2900afe..73ebf58 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,6 +1,6 @@ -module.exports = function(api) { +module.exports = function (api) { api.cache(true); return { - presets: ['babel-preset-expo'], + presets: ["babel-preset-expo"], }; }; diff --git a/package.json b/package.json index b282f41..149499a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test": "jest --watch --coverage=false", "testDebug": "jest -o --watch --coverage=false", "testFinal": "jest", - "updateSnapshots": "jest -u --coverage=false" + "updateSnapshots": "jest -u --coverage=false", + "lint": "eslint ." }, "jest": { "preset": "jest-expo", @@ -36,8 +37,13 @@ "@types/jest": "^29.5.12", "@types/react": "~18.2.45", "@types/react-test-renderer": "^18.3.0", + "eslint": "^8.57.0", + "eslint-config-expo": "^7.0.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", "jest": "^29.7.0", "jest-expo": "^51.0.2", + "prettier": "^3.3.3", "react-test-renderer": "18.2.0", "typescript": "^5.1.3" }, diff --git a/src/components/Board.tsx b/src/components/Board.tsx index 546ef88..41aed82 100644 --- a/src/components/Board.tsx +++ b/src/components/Board.tsx @@ -1,50 +1,50 @@ -import { View, StyleSheet } from "react-native"; -import Square from "./Square"; -import { GameBoard } from "../types/types"; -import { GAME_STATES, PLAYER_X } from "../constants/common"; - -interface Props { - board: GameBoard; - gameState: string; - disabled?: boolean; - onPressUser: (rowIndex: number, colIndex: number) => void; -} - -/** - * This is a presentational (stateless) component for the game board - * - * @param param0 - * @returns - */ -function Board({ board, gameState, disabled, onPressUser }: Props) { - return ( - - {board.map((row, rowIndex) => ( - - {row.map((_, colIndex) => { - const value = board[rowIndex][colIndex]; - - return ( - onPressUser(rowIndex, colIndex)} - value={value ? (value === PLAYER_X ? "X" : "O") : null} - /> - ); - })} - - ))} - - ); -} - -const styles = StyleSheet.create({ - container: { - flexDirection: "row", - paddingVertical: 10, - }, -}); - -export default Board; +import { View, StyleSheet } from "react-native"; +import Square from "./Square"; +import { GameBoard } from "../types/types"; +import { GAME_STATES, PLAYER_X } from "../constants/common"; + +interface Props { + board: GameBoard; + gameState: string; + disabled?: boolean; + onPressUser: (rowIndex: number, colIndex: number) => void; +} + +/** + * This is a presentational (stateless) component for the game board + * + * @param param0 + * @returns + */ +function Board({ board, gameState, disabled, onPressUser }: Props) { + return ( + + {board.map((row, rowIndex) => ( + + {row.map((_, colIndex) => { + const value = board[rowIndex][colIndex]; + + return ( + onPressUser(rowIndex, colIndex)} + value={value ? (value === PLAYER_X ? "X" : "O") : null} + /> + ); + })} + + ))} + + ); +} + +const styles = StyleSheet.create({ + container: { + flexDirection: "row", + paddingVertical: 10, + }, +}); + +export default Board; diff --git a/src/components/Game.tsx b/src/components/Game.tsx index 6561039..25bf05d 100644 --- a/src/components/Game.tsx +++ b/src/components/Game.tsx @@ -1,253 +1,253 @@ -import { View, StyleSheet, Text, TouchableOpacity } from "react-native"; -import { useCallback, useEffect, useState } from "react"; -import Board from "./Board"; -import { BOARD_SIZES, GAME_STATES, PLAYER_X } from "../constants/common"; -import { - createBoard, - getComputerValue, - getRandomMove, - getUserValue, - getWinner, - getWinnerText, - isComputerVsComputer, - isUserVsUser, - switchPlayer, -} from "../utils/common"; -import { displayAlert } from "../utils/alert"; -import { GameBoard, GameSettings, Players } from "../types/types"; -import Settings from "./Settings"; -import { Colors } from "../constants/colors"; -import { getAiMove } from "../utils/ai"; - -// create a 3x3 as the default board -const emptyBoard = createBoard(BOARD_SIZES._3x3); - -/** - * - * Core component for handling game logic and implementation - */ -function Game() { - const [board, setBoard] = useState(emptyBoard); - const [gameState, setGameState] = useState(GAME_STATES.notStarted); - const [nextMove, setNextMove] = useState(null); - const [winner, setWinner] = useState(null); - const [players, setPlayers] = useState({ - user: null, - computer: null, - }); - const [currentPlayer, setCurrentPlayer] = useState(null); - const [gameSettings, setGameSettings] = useState(null); - - // This function handles updating the board, invoked by userMove or computerMove function - const updateBoard = useCallback( - (row: number, col: number, player: number | null) => { - player && - gameState === GAME_STATES.inProgress && - setBoard((board) => { - const boardCopy = board.map((arr) => arr.slice()); - boardCopy[row][col] = player; - return boardCopy; - }); - }, - [gameState] - ); - - //This function handles the user's interaction on the board - const userMove = (row: number, col: number) => { - if (!board[row][col] && nextMove === players.user) { - const currPlayer = currentPlayer ? switchPlayer(currentPlayer) : nextMove; - - //if it's user vs. user, let the opposite of player X or O update the board - updateBoard(row, col, players.computer ? players.user : currPlayer); - - //if it's user vs. user, let the user make the next move - setNextMove(players.computer ? players.computer : players.user); - setCurrentPlayer(currPlayer); - } - }; - - //This function handles the computer's interaction on the board - const computerMove = useCallback(() => { - const currPlayer = currentPlayer ? switchPlayer(currentPlayer) : nextMove; - - // if AI is incorporated, call getAiMove, otherwise call getRandomMove - const [row, col] = gameSettings?.useAi - ? getAiMove(board, gameSettings, currPlayer) - : getRandomMove(board, gameSettings?.boardSize); - - //if it's computer vs. computer, let the opposite of player X or O update the board - updateBoard(row, col, players.user ? players.computer : currPlayer); - - //if it's computer vs. computer, let the computer make the next move - setNextMove(players.user ? players.user : players.computer); - setCurrentPlayer(currPlayer); - }, [board, players, currentPlayer, updateBoard]); - - // handler for when game is started - const handleStartGame = (settings: GameSettings) => { - setGameSettings(settings); - - const userVsUser = isUserVsUser(settings); - const computerVsComputer = isComputerVsComputer(settings); - - const user = getUserValue(settings); - const computer = getComputerValue(settings); - - setBoard(createBoard(settings.boardSize ?? BOARD_SIZES._3x3)); - setGameState(GAME_STATES.inProgress); - setPlayers({ - user: computerVsComputer ? null : user, - computer: userVsUser ? null : computer, - }); - - //Player X always makes the first move - setNextMove(PLAYER_X); - setCurrentPlayer(null); - }; - - //Reset all states when game is started - const startNewGame = () => { - setGameState(GAME_STATES.notStarted); - setBoard(emptyBoard); - setWinner(null); - setNextMove(null); - setCurrentPlayer(null); - setGameSettings(null); - }; - - //We need this to keep track of whose next move is, and also ensure the right track of turns - useEffect(() => { - let timeout: any; - const isComputerMove = - nextMove && - nextMove === players.computer && - gameState !== GAME_STATES.ended; - - if (isComputerMove) { - timeout = setTimeout( - () => { - computerMove(); - }, - // Give it more time when it's a computer .vs computer - players.user ? 500 : 1200 - ); - } - return () => { - // clean up timeout - if (timeout) { - clearTimeout(timeout); - } - }; - }, [gameState, nextMove, players.computer, computerMove]); - - useEffect(() => { - if (gameState !== GAME_STATES.ended) { - const winnerValue = getWinner(board, gameSettings?.boardSize); - - if (winnerValue !== null) { - //Return either a win or tie - const winnerText = getWinnerText(winnerValue); - - setGameState(GAME_STATES.ended); - setWinner(winnerText); - setNextMove(null); - displayAlert("Game over", winnerText); - } - } - }, [board, nextMove, gameState]); - - const getPlayersTurnInfo = () => { - let playerStateInfo = ""; - const playerVsItself = !players.user || !players.computer; - - if (playerVsItself) { - playerStateInfo = winner - ? winner - : currentPlayer === PLAYER_X - ? "Player O's turn" - : "Player X's turn"; - } else { - playerStateInfo = winner - ? winner - : nextMove === PLAYER_X - ? "Player X's turn" - : "Player O's turn"; - } - - return playerStateInfo; - }; - - const gameStateInfo = - gameState === GAME_STATES.inProgress ? "Game in progress" : "Game over"; - - return gameState === GAME_STATES.notStarted ? ( - - Tic Tac Toe - handleStartGame(settings)} /> - - ) : ( - - - {`${gameSettings?.playerX?.toUpperCase()} .vs ${gameSettings?.playerO?.toUpperCase()}`} - {gameStateInfo} - - userMove(rowIndex, colIndex)} - /> - - {getPlayersTurnInfo()} - - startNewGame()}> - Start over - - - ); -} - -const styles = StyleSheet.create({ - boardContainer: {}, - headerContainer: { - alignItems: "center", - paddingHorizontal: 10, - }, - title: { - fontSize: 38, - padding: 10, - fontWeight: "bold", - }, - resetBtn: { - padding: 10, - marginTop: 24, - borderColor: Colors.gray, - borderWidth: 1, - borderRadius: 5, - alignItems: "center", - }, - resetText: { - fontSize: 20, - }, - gameStateText: { - fontSize: 18, - }, - gameStateTextContainer: { - alignItems: "center", - rowGap: 20, - }, - playerText: { - fontSize: 16, - }, - playersInfoText: { - fontSize: 18, - fontWeight: "bold", - }, - playerTextContainer: { - alignItems: "center", - }, -}); - -export default Game; +import { View, StyleSheet, Text, TouchableOpacity } from "react-native"; +import { useCallback, useEffect, useState } from "react"; +import Board from "./Board"; +import { BOARD_SIZES, GAME_STATES, PLAYER_X } from "../constants/common"; +import { + createBoard, + getComputerValue, + getRandomMove, + getUserValue, + getWinner, + getWinnerText, + isComputerVsComputer, + isUserVsUser, + switchPlayer, +} from "../utils/common"; +import { displayAlert } from "../utils/alert"; +import { GameBoard, GameSettings, Players } from "../types/types"; +import Settings from "./Settings"; +import { Colors } from "../constants/colors"; +import { getAiMove } from "../utils/ai"; + +// create a 3x3 as the default board +const emptyBoard = createBoard(BOARD_SIZES._3x3); + +/** + * + * Core component for handling game logic and implementation + */ +function Game() { + const [board, setBoard] = useState(emptyBoard); + const [gameState, setGameState] = useState(GAME_STATES.notStarted); + const [nextMove, setNextMove] = useState(null); + const [winner, setWinner] = useState(null); + const [players, setPlayers] = useState({ + user: null, + computer: null, + }); + const [currentPlayer, setCurrentPlayer] = useState(null); + const [gameSettings, setGameSettings] = useState(null); + + // This function handles updating the board, invoked by userMove or computerMove function + const updateBoard = useCallback( + (row: number, col: number, player: number | null) => { + player && + gameState === GAME_STATES.inProgress && + setBoard((board) => { + const boardCopy = board.map((arr) => arr.slice()); + boardCopy[row][col] = player; + return boardCopy; + }); + }, + [gameState], + ); + + //This function handles the user's interaction on the board + const userMove = (row: number, col: number) => { + if (!board[row][col] && nextMove === players.user) { + const currPlayer = currentPlayer ? switchPlayer(currentPlayer) : nextMove; + + //if it's user vs. user, let the opposite of player X or O update the board + updateBoard(row, col, players.computer ? players.user : currPlayer); + + //if it's user vs. user, let the user make the next move + setNextMove(players.computer ? players.computer : players.user); + setCurrentPlayer(currPlayer); + } + }; + + //This function handles the computer's interaction on the board + const computerMove = useCallback(() => { + const currPlayer = currentPlayer ? switchPlayer(currentPlayer) : nextMove; + + // if AI is incorporated, call getAiMove, otherwise call getRandomMove + const [row, col] = gameSettings?.useAi + ? getAiMove(board, gameSettings, currPlayer) + : getRandomMove(board, gameSettings?.boardSize); + + //if it's computer vs. computer, let the opposite of player X or O update the board + updateBoard(row, col, players.user ? players.computer : currPlayer); + + //if it's computer vs. computer, let the computer make the next move + setNextMove(players.user ? players.user : players.computer); + setCurrentPlayer(currPlayer); + }, [board, players, currentPlayer, updateBoard]); + + // handler for when game is started + const handleStartGame = (settings: GameSettings) => { + setGameSettings(settings); + + const userVsUser = isUserVsUser(settings); + const computerVsComputer = isComputerVsComputer(settings); + + const user = getUserValue(settings); + const computer = getComputerValue(settings); + + setBoard(createBoard(settings.boardSize ?? BOARD_SIZES._3x3)); + setGameState(GAME_STATES.inProgress); + setPlayers({ + user: computerVsComputer ? null : user, + computer: userVsUser ? null : computer, + }); + + //Player X always makes the first move + setNextMove(PLAYER_X); + setCurrentPlayer(null); + }; + + //Reset all states when game is started + const startNewGame = () => { + setGameState(GAME_STATES.notStarted); + setBoard(emptyBoard); + setWinner(null); + setNextMove(null); + setCurrentPlayer(null); + setGameSettings(null); + }; + + //We need this to keep track of whose next move is, and also ensure the right track of turns + useEffect(() => { + let timeout: any; + const isComputerMove = + nextMove && + nextMove === players.computer && + gameState !== GAME_STATES.ended; + + if (isComputerMove) { + timeout = setTimeout( + () => { + computerMove(); + }, + // Give it more time when it's a computer .vs computer + players.user ? 500 : 1200, + ); + } + return () => { + // clean up timeout + if (timeout) { + clearTimeout(timeout); + } + }; + }, [gameState, nextMove, players.computer, computerMove]); + + useEffect(() => { + if (gameState !== GAME_STATES.ended) { + const winnerValue = getWinner(board, gameSettings?.boardSize); + + if (winnerValue !== null) { + //Return either a win or tie + const winnerText = getWinnerText(winnerValue); + + setGameState(GAME_STATES.ended); + setWinner(winnerText); + setNextMove(null); + displayAlert("Game over", winnerText); + } + } + }, [board, nextMove, gameState]); + + const getPlayersTurnInfo = () => { + let playerStateInfo = ""; + const playerVsItself = !players.user || !players.computer; + + if (playerVsItself) { + playerStateInfo = winner + ? winner + : currentPlayer === PLAYER_X + ? "Player O's turn" + : "Player X's turn"; + } else { + playerStateInfo = winner + ? winner + : nextMove === PLAYER_X + ? "Player X's turn" + : "Player O's turn"; + } + + return playerStateInfo; + }; + + const gameStateInfo = + gameState === GAME_STATES.inProgress ? "Game in progress" : "Game over"; + + return gameState === GAME_STATES.notStarted ? ( + + Tic Tac Toe + handleStartGame(settings)} /> + + ) : ( + + + {`${gameSettings?.playerX?.toUpperCase()} .vs ${gameSettings?.playerO?.toUpperCase()}`} + {gameStateInfo} + + userMove(rowIndex, colIndex)} + /> + + {getPlayersTurnInfo()} + + startNewGame()}> + Start over + + + ); +} + +const styles = StyleSheet.create({ + boardContainer: {}, + headerContainer: { + alignItems: "center", + paddingHorizontal: 10, + }, + title: { + fontSize: 38, + padding: 10, + fontWeight: "bold", + }, + resetBtn: { + padding: 10, + marginTop: 24, + borderColor: Colors.gray, + borderWidth: 1, + borderRadius: 5, + alignItems: "center", + }, + resetText: { + fontSize: 20, + }, + gameStateText: { + fontSize: 18, + }, + gameStateTextContainer: { + alignItems: "center", + rowGap: 20, + }, + playerText: { + fontSize: 16, + }, + playersInfoText: { + fontSize: 18, + fontWeight: "bold", + }, + playerTextContainer: { + alignItems: "center", + }, +}); + +export default Game; diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 98d15a7..3bfdeb0 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -1,266 +1,266 @@ -import { - View, - StyleSheet, - TouchableOpacity, - Text, - Platform, -} from "react-native"; -import { BOARD_SIZES, PLAYER_TYPES } from "../constants/common"; -import React, { useEffect, useState } from "react"; -import { Picker } from "@react-native-picker/picker"; -import { GameSettings, PlayerTypes } from "../types/types"; -import RadioButton from "./ui/RadioButton"; -import { Colors } from "../constants/colors"; -import Checkbox from "expo-checkbox"; - -interface Props { - onStartGame: (settings: GameSettings) => void; -} - -type GameSettingsKeys = keyof GameSettings; - -function Settings({ onStartGame }: Props) { - const [settings, setSettings] = useState({ - playerX: "user", - playerO: "computer", - useAi: false, - boardSize: BOARD_SIZES._3x3, - }); - const [isUseAiChecked, setIsUseAiChecked] = useState(false); - - const handleBoardSizePress = (value: number) => { - setSettings((currentSettings) => ({ - ...currentSettings, - boardSize: value, - })); - }; - - const handleAiCheckboxPress = (value: boolean) => { - setIsUseAiChecked(value); - setSettings((currentSettings) => ({ - ...currentSettings, - useAi: value, - })); - }; - - const handlePlayerDropdownPress = ( - value: PlayerTypes | null, - key: GameSettingsKeys - ) => { - setSettings((currentSettings) => ({ - ...currentSettings, - [key]: value, - })); - }; - - const disableStartGameBtn = !settings.playerX || !settings.playerO; - - const disableAiCheckbox = - (settings.playerX !== PLAYER_TYPES.computer && - settings.playerO !== PLAYER_TYPES.computer) || - //TODO: Currently AI is only available for 3x3 board. - //There seems to be a performance issue that might cause your device (or virtual device) - //to freeze when using the AI Algorithm for other board sizes. - settings.boardSize !== BOARD_SIZES._3x3; - - useEffect(() => { - disableAiCheckbox && setIsUseAiChecked(false); - }, [disableAiCheckbox]); - - return ( - - - - - handleBoardSizePress(BOARD_SIZES._3x3)} - /> - 3x3 - - - handleBoardSizePress(BOARD_SIZES._4x4)} - /> - 4x4 - - - handleBoardSizePress(BOARD_SIZES._5x5)} - /> - 5x5 - - - - - Incorporate AI - - - Select player "X" and "O" to start the game - - - - - style={styles.picker} - mode="dropdown" - selectedValue={settings.playerX} - onValueChange={(itemValue) => - handlePlayerDropdownPress(itemValue, "playerX") - } - > - {Platform.OS === "android" && ( - - )} - - - - - - - style={styles.picker} - mode="dropdown" - selectedValue={settings.playerO} - onValueChange={(itemValue) => - handlePlayerDropdownPress(itemValue, "playerO") - } - > - {Platform.OS === "android" && ( - - )} - - - - - - - - onStartGame(settings)} - disabled={disableStartGameBtn} - > - - Start game - - - - - ); -} - -const getAndroidPickerStyles = () => - Platform.OS === "android" && { - borderRadius: 10, - borderWidth: 1, - borderColor: Colors.gray, - }; - -const styles = StyleSheet.create({ - container: { - rowGap: 32, - }, - boardSizesContainer: { - flexDirection: "row", - gap: 20, - alignItems: "center", - }, - settingsContainer: { - alignItems: "center", - paddingHorizontal: 15, - }, - selectPlayersText: { - fontSize: 16, - paddingBottom: Platform.OS === "ios" ? 70 : 10, - paddingTop: 20, - }, - selectorBtn: { - alignItems: "center", - backgroundColor: Colors.grayLight, - padding: 10, - margin: 10, - width: "100%", - borderRadius: 5, - }, - startGameText: { - fontSize: 18, - }, - pickersContainer: { - flexDirection: "row", - gap: 10, - }, - pickerContainer: { - height: 40, - flexDirection: "row", - alignItems: "center", - width: "50%", - }, - picker: { - width: "100%", - }, - pickerItem: { - color: "gray", - }, - radioBtnContainer: { - flexDirection: "row", - justifyContent: "center", - alignItems: "center", - paddingVertical: 20, - padding: 20, - gap: 5, - }, - radioBtnText: { - fontSize: 18, - }, - startGameBtnContainer: { - justifyContent: "center", - alignItems: "center", - paddingHorizontal: 15, - paddingTop: 8, - }, - checkboxContainer: { - marginHorizontal: 16, - marginVertical: 20, - flexDirection: "row", - alignItems: "center", - }, - section: { - flexDirection: "row", - alignItems: "center", - }, - paragraph: { - fontSize: 15, - }, - checkbox: { - margin: 8, - }, - checkboxText: { - fontSize: 15, - }, -}); - -export default Settings; +import { + View, + StyleSheet, + TouchableOpacity, + Text, + Platform, +} from "react-native"; +import { BOARD_SIZES, PLAYER_TYPES } from "../constants/common"; +import React, { useEffect, useState } from "react"; +import { Picker } from "@react-native-picker/picker"; +import { GameSettings, PlayerTypes } from "../types/types"; +import RadioButton from "./ui/RadioButton"; +import { Colors } from "../constants/colors"; +import Checkbox from "expo-checkbox"; + +interface Props { + onStartGame: (settings: GameSettings) => void; +} + +type GameSettingsKeys = keyof GameSettings; + +function Settings({ onStartGame }: Props) { + const [settings, setSettings] = useState({ + playerX: "user", + playerO: "computer", + useAi: false, + boardSize: BOARD_SIZES._3x3, + }); + const [isUseAiChecked, setIsUseAiChecked] = useState(false); + + const handleBoardSizePress = (value: number) => { + setSettings((currentSettings) => ({ + ...currentSettings, + boardSize: value, + })); + }; + + const handleAiCheckboxPress = (value: boolean) => { + setIsUseAiChecked(value); + setSettings((currentSettings) => ({ + ...currentSettings, + useAi: value, + })); + }; + + const handlePlayerDropdownPress = ( + value: PlayerTypes | null, + key: GameSettingsKeys, + ) => { + setSettings((currentSettings) => ({ + ...currentSettings, + [key]: value, + })); + }; + + const disableStartGameBtn = !settings.playerX || !settings.playerO; + + const disableAiCheckbox = + (settings.playerX !== PLAYER_TYPES.computer && + settings.playerO !== PLAYER_TYPES.computer) || + //TODO: Currently AI is only available for 3x3 board. + //There seems to be a performance issue that might cause your device (or virtual device) + //to freeze when using the AI Algorithm for other board sizes. + settings.boardSize !== BOARD_SIZES._3x3; + + useEffect(() => { + disableAiCheckbox && setIsUseAiChecked(false); + }, [disableAiCheckbox]); + + return ( + + + + + handleBoardSizePress(BOARD_SIZES._3x3)} + /> + 3x3 + + + handleBoardSizePress(BOARD_SIZES._4x4)} + /> + 4x4 + + + handleBoardSizePress(BOARD_SIZES._5x5)} + /> + 5x5 + + + + + Incorporate AI + + + Select player "X" and "O" to start the game + + + + + style={styles.picker} + mode="dropdown" + selectedValue={settings.playerX} + onValueChange={(itemValue) => + handlePlayerDropdownPress(itemValue, "playerX") + } + > + {Platform.OS === "android" && ( + + )} + + + + + + + style={styles.picker} + mode="dropdown" + selectedValue={settings.playerO} + onValueChange={(itemValue) => + handlePlayerDropdownPress(itemValue, "playerO") + } + > + {Platform.OS === "android" && ( + + )} + + + + + + + + onStartGame(settings)} + disabled={disableStartGameBtn} + > + + Start game + + + + + ); +} + +const getAndroidPickerStyles = () => + Platform.OS === "android" && { + borderRadius: 10, + borderWidth: 1, + borderColor: Colors.gray, + }; + +const styles = StyleSheet.create({ + container: { + rowGap: 32, + }, + boardSizesContainer: { + flexDirection: "row", + gap: 20, + alignItems: "center", + }, + settingsContainer: { + alignItems: "center", + paddingHorizontal: 15, + }, + selectPlayersText: { + fontSize: 16, + paddingBottom: Platform.OS === "ios" ? 70 : 10, + paddingTop: 20, + }, + selectorBtn: { + alignItems: "center", + backgroundColor: Colors.grayLight, + padding: 10, + margin: 10, + width: "100%", + borderRadius: 5, + }, + startGameText: { + fontSize: 18, + }, + pickersContainer: { + flexDirection: "row", + gap: 10, + }, + pickerContainer: { + height: 40, + flexDirection: "row", + alignItems: "center", + width: "50%", + }, + picker: { + width: "100%", + }, + pickerItem: { + color: "gray", + }, + radioBtnContainer: { + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + paddingVertical: 20, + padding: 20, + gap: 5, + }, + radioBtnText: { + fontSize: 18, + }, + startGameBtnContainer: { + justifyContent: "center", + alignItems: "center", + paddingHorizontal: 15, + paddingTop: 8, + }, + checkboxContainer: { + marginHorizontal: 16, + marginVertical: 20, + flexDirection: "row", + alignItems: "center", + }, + section: { + flexDirection: "row", + alignItems: "center", + }, + paragraph: { + fontSize: 15, + }, + checkbox: { + margin: 8, + }, + checkboxText: { + fontSize: 15, + }, +}); + +export default Settings; diff --git a/src/components/Square.tsx b/src/components/Square.tsx index 95845bb..4d5a81b 100644 --- a/src/components/Square.tsx +++ b/src/components/Square.tsx @@ -1,43 +1,43 @@ -import { StyleSheet, Text, Dimensions, TouchableHighlight } from "react-native"; -import { Colors } from "../constants/colors"; - -const { width, height } = Dimensions.get("window"); - -console.log(width, height); - -interface Props { - value: string | null; - disabled?: boolean; - testID?: string; - onSquarePress: () => void; -} - -function Square({ value, disabled, testID, onSquarePress }: Props) { - return ( - - {value} - - ); -} - -const styles = StyleSheet.create({ - container: { - borderWidth: 1, - flexDirection: "row", - width: 76, - height: 76, - alignItems: "center", - justifyContent: "center", - }, - label: { - fontSize: 40, - }, -}); - -export default Square; +import { StyleSheet, Text, Dimensions, TouchableHighlight } from "react-native"; +import { Colors } from "../constants/colors"; + +const { width, height } = Dimensions.get("window"); + +console.log(width, height); + +interface Props { + value: string | null; + disabled?: boolean; + testID?: string; + onSquarePress: () => void; +} + +function Square({ value, disabled, testID, onSquarePress }: Props) { + return ( + + {value} + + ); +} + +const styles = StyleSheet.create({ + container: { + borderWidth: 1, + flexDirection: "row", + width: 76, + height: 76, + alignItems: "center", + justifyContent: "center", + }, + label: { + fontSize: 40, + }, +}); + +export default Square; diff --git a/src/constants/common.ts b/src/constants/common.ts index 1e5170a..d1f396b 100644 --- a/src/constants/common.ts +++ b/src/constants/common.ts @@ -1,20 +1,20 @@ -export const BOARD_SIZES = { - _3x3: 3, - _4x4: 4, - _5x5: 5, -}; - -export const PLAYER_X = 1; -export const PLAYER_O = 2; -export const TIE = 0; - -export const PLAYER_TYPES = { - user: "user", - computer: "computer", -}; - -export const GAME_STATES = { - notStarted: "not_started", - inProgress: "in_progress", - ended: "ended", -}; +export const BOARD_SIZES = { + _3x3: 3, + _4x4: 4, + _5x5: 5, +}; + +export const PLAYER_X = 1; +export const PLAYER_O = 2; +export const TIE = 0; + +export const PLAYER_TYPES = { + user: "user", + computer: "computer", +}; + +export const GAME_STATES = { + notStarted: "not_started", + inProgress: "in_progress", + ended: "ended", +}; diff --git a/src/types/types.ts b/src/types/types.ts index 8bb31dc..fb5ad94 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,12 +1,12 @@ -export type PlayerTypes = "user" | "computer"; -export type GameBoard = Array[]; -export type GameSettings = { - playerX: PlayerTypes | null; - playerO: PlayerTypes | null; - boardSize: number | null; - useAi: boolean; -}; -export type Players = { - user: number | null; - computer: number | null; -}; +export type PlayerTypes = "user" | "computer"; +export type GameBoard = (number | null)[][]; +export type GameSettings = { + playerX: PlayerTypes | null; + playerO: PlayerTypes | null; + boardSize: number | null; + useAi: boolean; +}; +export type Players = { + user: number | null; + computer: number | null; +}; diff --git a/src/utils/ai.ts b/src/utils/ai.ts index 23d2942..2428526 100644 --- a/src/utils/ai.ts +++ b/src/utils/ai.ts @@ -1,222 +1,222 @@ -import { - BOARD_SIZES, - PLAYER_O, - PLAYER_TYPES, - PLAYER_X, -} from "../constants/common"; -import { GameBoard, GameSettings } from "../types/types"; -import { isComputerVsComputer, switchPlayer } from "./common"; - -/** - * - * This file has functions for implementing AI using Minimax Algorithm - * - * Resource: https://www.geeksforgeeks.org/finding-optimal-move-in-tic-tac-toe-using-minimax-algorithm-in-game-theory/ - */ - -/** - * This function returns true if there are moves - remaining on the board. It returns false if - there are no moves left to play. - * @param board - * @param boardSize - * @returns - */ -function isMovesLeft(board: GameBoard, boardSize: number) { - for (let i = 0; i < boardSize; i++) - for (let j = 0; j < boardSize; j++) if (board[i][j] === null) return true; - - return false; -} - -/** - * This is the evaluation function as discussed - * in the previous article ( http://goo.gl/sJgv68 ) - * @param b - * @param human - * @param ai - * @param boardSize - * @returns - */ -function evaluate(b: GameBoard, human: number, ai: number, boardSize: number) { - // Checking for Rows for X or O victory. - for (let row = 0; row < boardSize; row++) { - if (b[row][0] == b[row][1] && b[row][1] == b[row][2]) { - if (b[row][0] == ai) return +10; - else if (b[row][0] == human) return -10; - } - } - - // Checking for Columns for X or O victory. - for (let col = 0; col < boardSize; col++) { - if (b[0][col] == b[1][col] && b[1][col] == b[2][col]) { - if (b[0][col] == ai) return +10; - else if (b[0][col] == human) return -10; - } - } - - // Checking for Diagonals for X or O victory. - if (b[0][0] == b[1][1] && b[1][1] == b[2][2]) { - if (b[0][0] == ai) return +10; - else if (b[0][0] == human) return -10; - } - - if (b[0][2] == b[1][1] && b[1][1] == b[2][0]) { - if (b[0][2] == ai) return +10; - else if (b[0][2] == human) return -10; - } - - // Else if none of them have - // won then return 0 - return 0; -} - -/** - * This is the minimax function. It - * considers all the possible ways - the game can go and returns the - value of the board - * @param board - * @param depth - * @param isMax - * @param human - * @param ai - * @param boardSize - * @returns - */ -function minimax( - board: GameBoard, - depth: number, - isMax: boolean, - human: number, - ai: number, - boardSize: number -) { - let score = evaluate(board, human, ai, boardSize); - - // If Maximizer has won the game - // return his/her evaluated score - if (score == 10) return score; - - // If Minimizer has won the game - // return his/her evaluated score - if (score == -10) return score; - - // If there are no more moves and - // no winner then it is a tie - if (isMovesLeft(board, boardSize) == false) return 0; - - // If this maximizer's move - if (isMax) { - let best = -1000; - - // Traverse all cells - for (let i = 0; i < boardSize; i++) { - for (let j = 0; j < boardSize; j++) { - // Check if cell is empty - if (board[i][j] === null) { - // Make the move - board[i][j] = ai; - - // Call minimax recursively - // and choose the maximum value - best = Math.max( - best, - minimax(board, depth + 1, !isMax, human, ai, boardSize) - ); - - // Undo the move - board[i][j] = null; - } - } - } - return best; - } - - // If this minimizer's move - else { - let best = 1000; - - // Traverse all cells - for (let i = 0; i < boardSize; i++) { - for (let j = 0; j < boardSize; j++) { - // Check if cell is empty - if (board[i][j] == null) { - // Make the move - board[i][j] = human; - - // Call minimax recursively and - // choose the minimum value - best = Math.min( - best, - minimax(board, depth + 1, !isMax, human, ai, boardSize) - ); - - // Undo the move - board[i][j] = null; - } - } - } - return best; - } -} - -// This will return the best possible -// move for the player -export function getAiMove( - board: GameBoard, - settings: GameSettings | null, - currentPlayer: number | null -) { - let human = PLAYER_X, - ai = PLAYER_O; - - const computerVsComputer = settings ? isComputerVsComputer(settings) : false; - - if (computerVsComputer) { - const currPlayer = currentPlayer ?? PLAYER_X; - human = switchPlayer(currPlayer); - ai = currPlayer; - } else { - human = settings?.playerX === PLAYER_TYPES.user ? PLAYER_X : PLAYER_O; - ai = switchPlayer(human); - } - - const boardSize = settings?.boardSize ?? BOARD_SIZES._3x3; - - let bestVal = -1000; - let row = -1; - let col = -1; - - // Traverse all cells, evaluate - // minimax function for all empty - // cells. And return the cell - // with optimal value. - for (let i = 0; i < boardSize; i++) { - for (let j = 0; j < boardSize; j++) { - // Check if cell is empty - if (board[i][j] === null) { - // Make the move - board[i][j] = ai; - - // compute evaluation function - // for this move. - let moveVal = minimax(board, 0, false, human, ai, boardSize); - - // Undo the move - board[i][j] = null; - - // If the value of the current move - // is more than the best value, then - // update best - if (moveVal > bestVal) { - row = i; - col = j; - bestVal = moveVal; - } - } - } - } - - return [row, col]; -} +import { + BOARD_SIZES, + PLAYER_O, + PLAYER_TYPES, + PLAYER_X, +} from "../constants/common"; +import { GameBoard, GameSettings } from "../types/types"; +import { isComputerVsComputer, switchPlayer } from "./common"; + +/** + * + * This file has functions for implementing AI using Minimax Algorithm + * + * Resource: https://www.geeksforgeeks.org/finding-optimal-move-in-tic-tac-toe-using-minimax-algorithm-in-game-theory/ + */ + +/** + * This function returns true if there are moves + remaining on the board. It returns false if + there are no moves left to play. + * @param board + * @param boardSize + * @returns + */ +function isMovesLeft(board: GameBoard, boardSize: number) { + for (let i = 0; i < boardSize; i++) + for (let j = 0; j < boardSize; j++) if (board[i][j] === null) return true; + + return false; +} + +/** + * This is the evaluation function as discussed + * in the previous article ( http://goo.gl/sJgv68 ) + * @param b + * @param human + * @param ai + * @param boardSize + * @returns + */ +function evaluate(b: GameBoard, human: number, ai: number, boardSize: number) { + // Checking for Rows for X or O victory. + for (let row = 0; row < boardSize; row++) { + if (b[row][0] === b[row][1] && b[row][1] === b[row][2]) { + if (b[row][0] === ai) return +10; + else if (b[row][0] === human) return -10; + } + } + + // Checking for Columns for X or O victory. + for (let col = 0; col < boardSize; col++) { + if (b[0][col] === b[1][col] && b[1][col] === b[2][col]) { + if (b[0][col] === ai) return +10; + else if (b[0][col] === human) return -10; + } + } + + // Checking for Diagonals for X or O victory. + if (b[0][0] === b[1][1] && b[1][1] === b[2][2]) { + if (b[0][0] === ai) return +10; + else if (b[0][0] === human) return -10; + } + + if (b[0][2] === b[1][1] && b[1][1] === b[2][0]) { + if (b[0][2] === ai) return +10; + else if (b[0][2] === human) return -10; + } + + // Else if none of them have + // won then return 0 + return 0; +} + +/** + * This is the minimax function. It + * considers all the possible ways + the game can go and returns the + value of the board + * @param board + * @param depth + * @param isMax + * @param human + * @param ai + * @param boardSize + * @returns + */ +function minimax( + board: GameBoard, + depth: number, + isMax: boolean, + human: number, + ai: number, + boardSize: number, +) { + let score = evaluate(board, human, ai, boardSize); + + // If Maximizer has won the game + // return his/her evaluated score + if (score === 10) return score; + + // If Minimizer has won the game + // return his/her evaluated score + if (score === -10) return score; + + // If there are no more moves and + // no winner then it is a tie + if (isMovesLeft(board, boardSize) === false) return 0; + + // If this maximizer's move + if (isMax) { + let best = -1000; + + // Traverse all cells + for (let i = 0; i < boardSize; i++) { + for (let j = 0; j < boardSize; j++) { + // Check if cell is empty + if (board[i][j] === null) { + // Make the move + board[i][j] = ai; + + // Call minimax recursively + // and choose the maximum value + best = Math.max( + best, + minimax(board, depth + 1, !isMax, human, ai, boardSize), + ); + + // Undo the move + board[i][j] = null; + } + } + } + return best; + } + + // If this minimizer's move + else { + let best = 1000; + + // Traverse all cells + for (let i = 0; i < boardSize; i++) { + for (let j = 0; j < boardSize; j++) { + // Check if cell is empty + if (board[i][j] == null) { + // Make the move + board[i][j] = human; + + // Call minimax recursively and + // choose the minimum value + best = Math.min( + best, + minimax(board, depth + 1, !isMax, human, ai, boardSize), + ); + + // Undo the move + board[i][j] = null; + } + } + } + return best; + } +} + +// This will return the best possible +// move for the player +export function getAiMove( + board: GameBoard, + settings: GameSettings | null, + currentPlayer: number | null, +) { + let human = PLAYER_X, + ai = PLAYER_O; + + const computerVsComputer = settings ? isComputerVsComputer(settings) : false; + + if (computerVsComputer) { + const currPlayer = currentPlayer ?? PLAYER_X; + human = switchPlayer(currPlayer); + ai = currPlayer; + } else { + human = settings?.playerX === PLAYER_TYPES.user ? PLAYER_X : PLAYER_O; + ai = switchPlayer(human); + } + + const boardSize = settings?.boardSize ?? BOARD_SIZES._3x3; + + let bestVal = -1000; + let row = -1; + let col = -1; + + // Traverse all cells, evaluate + // minimax function for all empty + // cells. And return the cell + // with optimal value. + for (let i = 0; i < boardSize; i++) { + for (let j = 0; j < boardSize; j++) { + // Check if cell is empty + if (board[i][j] === null) { + // Make the move + board[i][j] = ai; + + // compute evaluation function + // for this move. + let moveVal = minimax(board, 0, false, human, ai, boardSize); + + // Undo the move + board[i][j] = null; + + // If the value of the current move + // is more than the best value, then + // update best + if (moveVal > bestVal) { + row = i; + col = j; + bestVal = moveVal; + } + } + } + } + + return [row, col]; +} diff --git a/src/utils/alert.tsx b/src/utils/alert.tsx index a67ebb9..178c27c 100644 --- a/src/utils/alert.tsx +++ b/src/utils/alert.tsx @@ -3,7 +3,7 @@ import { Alert } from "react-native"; export const displayAlert = ( title: string, message: string, - onClose?: () => void + onClose?: () => void, ) => Alert.alert(title, message, [ { diff --git a/src/utils/common.ts b/src/utils/common.ts index a420f7f..8b3fd4f 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,264 +1,264 @@ -import { - TIE, - PLAYER_O, - PLAYER_X, - PLAYER_TYPES, - BOARD_SIZES, -} from "../constants/common"; -import { GameBoard, GameSettings } from "../types/types"; - -export const createBoard = (size: number) => - new Array(size).fill(Array(size).fill(null)); - -// Get a random number between min and max -export const getRandomNumber = (min: number, max: number) => { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; -}; - -export const switchPlayer = (player: number) => { - return player === PLAYER_X ? PLAYER_O : PLAYER_X; -}; - -/** - * This function is used to return a random move for the computer - * @param board - * @param boardSize - * @returns - */ -export const getRandomMove = (board: GameBoard, boardSize?: number | null) => { - const max = (boardSize ?? BOARD_SIZES._3x3) - 1; - let row = getRandomNumber(0, max); - let col = getRandomNumber(0, max); - - while (board[row][col]) { - row = getRandomNumber(0, max); - col = getRandomNumber(0, max); - } - - return [row, col]; -}; - -/** - * This function returns the game's final status based on player X and O, or tie - * @param winner - * @returns - */ -export const getWinnerText = (winner: number) => { - let winnerText = ""; - switch (winner) { - case PLAYER_X: - winnerText = "The winner is player X"; - break; - case PLAYER_O: - winnerText = "The winner is player O"; - break; - case TIE: - default: - winnerText = "It's a tie"; - } - - return winnerText; -}; - -/** - * This function calculates all winning possibilities, e.g. column, row, or diagonal in order to determine the winner - * - * TODO: This function can def be improved by calculating all possibilities dynamically - no hard coding - * @param board - * @param boardSize - * @returns - */ -export const getWinner = (board: GameBoard, boardSize?: number | null) => { - const dimensions = boardSize ?? BOARD_SIZES._3x3; - - const rowMarked = (row: number, board: GameBoard) => { - if (dimensions === BOARD_SIZES._3x3) { - return ( - board[row][0] !== null && - board[row][0] === board[row][1] && - board[row][1] === board[row][2] - ); - } - if (dimensions === BOARD_SIZES._4x4) { - return ( - board[row][0] !== null && - board[row][0] === board[row][1] && - board[row][1] === board[row][2] && - board[row][2] === board[row][3] - ); - } - if (dimensions === BOARD_SIZES._5x5) { - return ( - board[row][0] !== null && - board[row][0] === board[row][1] && - board[row][1] === board[row][2] && - board[row][2] === board[row][3] && - board[row][3] === board[row][4] - ); - } - }; - - const colMarked = (column: number, board: GameBoard) => { - if (dimensions === BOARD_SIZES._3x3) { - return ( - board[0][column] !== null && - board[0][column] === board[1][column] && - board[1][column] === board[2][column] - ); - } - if (dimensions === BOARD_SIZES._4x4) { - return ( - board[0][column] !== null && - board[0][column] === board[1][column] && - board[1][column] === board[2][column] && - board[2][column] === board[3][column] - ); - } - if (dimensions === BOARD_SIZES._5x5) { - return ( - board[0][column] !== null && - board[0][column] === board[1][column] && - board[1][column] === board[2][column] && - board[2][column] === board[3][column] && - board[3][column] === board[4][column] - ); - } - }; - - const diagonalMarked = (diagonal: number, board: GameBoard) => { - if (dimensions === BOARD_SIZES._3x3) { - if (diagonal === 1) { - return ( - board[0][0] !== null && - board[0][0] === board[1][1] && - board[1][1] === board[2][2] - ); - } else { - return ( - board[2][0] !== null && - board[2][0] === board[1][1] && - board[1][1] === board[0][2] - ); - } - } - if (dimensions === BOARD_SIZES._4x4) { - if (diagonal === 1) { - return ( - board[0][0] !== null && - board[0][0] === board[1][1] && - board[1][1] === board[2][2] && - board[2][2] === board[3][3] - ); - } else { - return ( - board[2][0] !== null && - board[3][0] === board[2][1] && - board[2][1] === board[1][2] && - board[1][2] === board[0][3] - ); - } - } - if (dimensions === BOARD_SIZES._5x5) { - if (diagonal === 1) { - return ( - board[0][0] !== null && - board[0][0] === board[1][1] && - board[1][1] === board[2][2] && - board[2][2] === board[3][3] && - board[3][3] === board[4][4] - ); - } else { - return ( - board[2][0] !== null && - board[4][0] === board[3][1] && - board[3][1] === board[2][2] && - board[2][2] === board[1][3] && - board[1][3] === board[0][4] - ); - } - } - }; - - if (dimensions === BOARD_SIZES._5x5 && rowMarked(4, board)) { - return board[4][0]; - } - if (dimensions === BOARD_SIZES._4x4 && rowMarked(3, board)) { - return board[3][0]; - } - - if (dimensions === BOARD_SIZES._5x5 && colMarked(4, board)) { - return board[0][4]; - } - if (dimensions === BOARD_SIZES._4x4 && colMarked(3, board)) { - return board[0][3]; - } - - if (dimensions === BOARD_SIZES._5x5 && diagonalMarked(2, board)) { - return board[4][0]; - } - if (dimensions === BOARD_SIZES._4x4 && diagonalMarked(2, board)) { - return board[3][0]; - } - - if (rowMarked(0, board)) { - return board[0][0]; - } - if (rowMarked(1, board)) { - return board[1][0]; - } - if (rowMarked(2, board)) { - return board[2][0]; - } - - if (colMarked(0, board)) { - return board[0][0]; - } - if (colMarked(1, board)) { - return board[0][1]; - } - if (colMarked(2, board)) { - return board[0][2]; - } - - if (diagonalMarked(1, board)) { - return board[0][0]; - } - if (diagonalMarked(2, board)) { - return board[2][0]; - } - - // if we get here, we need to check for any empty spot before we determine if game is a tie - for (let row = 0; row < board.length; row++) { - for (let col = 0; col < board[row].length; col++) { - if (board[row][col] === null) { - return null; - } - } - } - - return TIE; -}; - -export const isUserVsUser = (settings: GameSettings) => - settings.playerX === PLAYER_TYPES.user && - settings.playerO === PLAYER_TYPES.user; - -export const isComputerVsComputer = (settings: GameSettings) => - settings.playerX === PLAYER_TYPES.computer && - settings.playerO === PLAYER_TYPES.computer; - -export const getUserValue = (settings: GameSettings) => - isUserVsUser(settings) - ? PLAYER_X - : settings.playerX === PLAYER_TYPES.user - ? PLAYER_X - : PLAYER_O; - -export const getComputerValue = (settings: GameSettings) => - isComputerVsComputer(settings) - ? PLAYER_X - : settings.playerO === PLAYER_TYPES.computer - ? PLAYER_O - : PLAYER_X; +import { + TIE, + PLAYER_O, + PLAYER_X, + PLAYER_TYPES, + BOARD_SIZES, +} from "../constants/common"; +import { GameBoard, GameSettings } from "../types/types"; + +export const createBoard = (size: number) => + new Array(size).fill(Array(size).fill(null)); + +// Get a random number between min and max +export const getRandomNumber = (min: number, max: number) => { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +}; + +export const switchPlayer = (player: number) => { + return player === PLAYER_X ? PLAYER_O : PLAYER_X; +}; + +/** + * This function is used to return a random move for the computer + * @param board + * @param boardSize + * @returns + */ +export const getRandomMove = (board: GameBoard, boardSize?: number | null) => { + const max = (boardSize ?? BOARD_SIZES._3x3) - 1; + let row = getRandomNumber(0, max); + let col = getRandomNumber(0, max); + + while (board[row][col]) { + row = getRandomNumber(0, max); + col = getRandomNumber(0, max); + } + + return [row, col]; +}; + +/** + * This function returns the game's final status based on player X and O, or tie + * @param winner + * @returns + */ +export const getWinnerText = (winner: number) => { + let winnerText = ""; + switch (winner) { + case PLAYER_X: + winnerText = "The winner is player X"; + break; + case PLAYER_O: + winnerText = "The winner is player O"; + break; + case TIE: + default: + winnerText = "It's a tie"; + } + + return winnerText; +}; + +/** + * This function calculates all winning possibilities, e.g. column, row, or diagonal in order to determine the winner + * + * TODO: This function can def be improved by calculating all possibilities dynamically - no hard coding + * @param board + * @param boardSize + * @returns + */ +export const getWinner = (board: GameBoard, boardSize?: number | null) => { + const dimensions = boardSize ?? BOARD_SIZES._3x3; + + const rowMarked = (row: number, board: GameBoard) => { + if (dimensions === BOARD_SIZES._3x3) { + return ( + board[row][0] !== null && + board[row][0] === board[row][1] && + board[row][1] === board[row][2] + ); + } + if (dimensions === BOARD_SIZES._4x4) { + return ( + board[row][0] !== null && + board[row][0] === board[row][1] && + board[row][1] === board[row][2] && + board[row][2] === board[row][3] + ); + } + if (dimensions === BOARD_SIZES._5x5) { + return ( + board[row][0] !== null && + board[row][0] === board[row][1] && + board[row][1] === board[row][2] && + board[row][2] === board[row][3] && + board[row][3] === board[row][4] + ); + } + }; + + const colMarked = (column: number, board: GameBoard) => { + if (dimensions === BOARD_SIZES._3x3) { + return ( + board[0][column] !== null && + board[0][column] === board[1][column] && + board[1][column] === board[2][column] + ); + } + if (dimensions === BOARD_SIZES._4x4) { + return ( + board[0][column] !== null && + board[0][column] === board[1][column] && + board[1][column] === board[2][column] && + board[2][column] === board[3][column] + ); + } + if (dimensions === BOARD_SIZES._5x5) { + return ( + board[0][column] !== null && + board[0][column] === board[1][column] && + board[1][column] === board[2][column] && + board[2][column] === board[3][column] && + board[3][column] === board[4][column] + ); + } + }; + + const diagonalMarked = (diagonal: number, board: GameBoard) => { + if (dimensions === BOARD_SIZES._3x3) { + if (diagonal === 1) { + return ( + board[0][0] !== null && + board[0][0] === board[1][1] && + board[1][1] === board[2][2] + ); + } else { + return ( + board[2][0] !== null && + board[2][0] === board[1][1] && + board[1][1] === board[0][2] + ); + } + } + if (dimensions === BOARD_SIZES._4x4) { + if (diagonal === 1) { + return ( + board[0][0] !== null && + board[0][0] === board[1][1] && + board[1][1] === board[2][2] && + board[2][2] === board[3][3] + ); + } else { + return ( + board[2][0] !== null && + board[3][0] === board[2][1] && + board[2][1] === board[1][2] && + board[1][2] === board[0][3] + ); + } + } + if (dimensions === BOARD_SIZES._5x5) { + if (diagonal === 1) { + return ( + board[0][0] !== null && + board[0][0] === board[1][1] && + board[1][1] === board[2][2] && + board[2][2] === board[3][3] && + board[3][3] === board[4][4] + ); + } else { + return ( + board[2][0] !== null && + board[4][0] === board[3][1] && + board[3][1] === board[2][2] && + board[2][2] === board[1][3] && + board[1][3] === board[0][4] + ); + } + } + }; + + if (dimensions === BOARD_SIZES._5x5 && rowMarked(4, board)) { + return board[4][0]; + } + if (dimensions === BOARD_SIZES._4x4 && rowMarked(3, board)) { + return board[3][0]; + } + + if (dimensions === BOARD_SIZES._5x5 && colMarked(4, board)) { + return board[0][4]; + } + if (dimensions === BOARD_SIZES._4x4 && colMarked(3, board)) { + return board[0][3]; + } + + if (dimensions === BOARD_SIZES._5x5 && diagonalMarked(2, board)) { + return board[4][0]; + } + if (dimensions === BOARD_SIZES._4x4 && diagonalMarked(2, board)) { + return board[3][0]; + } + + if (rowMarked(0, board)) { + return board[0][0]; + } + if (rowMarked(1, board)) { + return board[1][0]; + } + if (rowMarked(2, board)) { + return board[2][0]; + } + + if (colMarked(0, board)) { + return board[0][0]; + } + if (colMarked(1, board)) { + return board[0][1]; + } + if (colMarked(2, board)) { + return board[0][2]; + } + + if (diagonalMarked(1, board)) { + return board[0][0]; + } + if (diagonalMarked(2, board)) { + return board[2][0]; + } + + // if we get here, we need to check for any empty spot before we determine if game is a tie + for (let row = 0; row < board.length; row++) { + for (let col = 0; col < board[row].length; col++) { + if (board[row][col] === null) { + return null; + } + } + } + + return TIE; +}; + +export const isUserVsUser = (settings: GameSettings) => + settings.playerX === PLAYER_TYPES.user && + settings.playerO === PLAYER_TYPES.user; + +export const isComputerVsComputer = (settings: GameSettings) => + settings.playerX === PLAYER_TYPES.computer && + settings.playerO === PLAYER_TYPES.computer; + +export const getUserValue = (settings: GameSettings) => + isUserVsUser(settings) + ? PLAYER_X + : settings.playerX === PLAYER_TYPES.user + ? PLAYER_X + : PLAYER_O; + +export const getComputerValue = (settings: GameSettings) => + isComputerVsComputer(settings) + ? PLAYER_X + : settings.playerO === PLAYER_TYPES.computer + ? PLAYER_O + : PLAYER_X; diff --git a/yarn.lock b/yarn.lock index 54a1ca1..d624373 100644 --- a/yarn.lock +++ b/yarn.lock @@ -797,6 +797,38 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + "@expo/bunyan@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@expo/bunyan/-/bunyan-4.0.0.tgz#be0c1de943c7987a9fbd309ea0b1acd605890c7b" @@ -1131,6 +1163,25 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -1427,7 +1478,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -1447,6 +1498,11 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== + "@react-native-community/cli-clean@13.6.8": version "13.6.8" resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-13.6.8.tgz#95ce964047f005152ac100394b6dcd5d2cc2a474" @@ -2000,6 +2056,11 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -2078,6 +2139,92 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@^7.4.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz#b16d3cf3ee76bf572fdf511e79c248bdec619ea3" + integrity sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/type-utils" "7.18.0" + "@typescript-eslint/utils" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^7.4.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.18.0.tgz#83928d0f1b7f4afa974098c64b5ce6f9051f96a0" + integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== + dependencies: + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/typescript-estree" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz#c928e7a9fc2c0b3ed92ab3112c614d6bd9951c83" + integrity sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA== + dependencies: + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" + +"@typescript-eslint/type-utils@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz#2165ffaee00b1fbbdd2d40aa85232dab6998f53b" + integrity sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA== + dependencies: + "@typescript-eslint/typescript-estree" "7.18.0" + "@typescript-eslint/utils" "7.18.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@7.18.0", "@typescript-eslint/types@^7.2.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" + integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== + +"@typescript-eslint/typescript-estree@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz#b5868d486c51ce8f312309ba79bdb9f331b37931" + integrity sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA== + dependencies: + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@7.18.0", "@typescript-eslint/utils@^7.2.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.18.0.tgz#bca01cde77f95fc6a8d5b0dbcbfb3d6ca4be451f" + integrity sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/typescript-estree" "7.18.0" + +"@typescript-eslint/visitor-keys@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz#0564629b6124d67607378d0f0332a0495b25e7d7" + integrity sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg== + dependencies: + "@typescript-eslint/types" "7.18.0" + eslint-visitor-keys "^3.4.3" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + "@urql/core@2.3.6": version "2.3.6" resolved "https://registry.yarnpkg.com/@urql/core/-/core-2.3.6.tgz#ee0a6f8fde02251e9560c5f17dce5cd90f948552" @@ -2140,6 +2287,11 @@ acorn-globals@^7.0.0: acorn "^8.1.0" acorn-walk "^8.0.2" +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-walk@^8.0.2: version "8.3.3" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" @@ -2147,7 +2299,7 @@ acorn-walk@^8.0.2: dependencies: acorn "^8.11.0" -acorn@^8.1.0, acorn@^8.11.0, acorn@^8.8.1, acorn@^8.8.2: +acorn@^8.1.0, acorn@^8.11.0, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -2167,6 +2319,16 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + anser@^1.4.9: version "1.4.10" resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b" @@ -2280,11 +2442,78 @@ array-buffer-byte-length@^1.0.1: call-bind "^1.0.5" is-array-buffer "^3.0.4" +array-includes@^3.1.6, array-includes@^3.1.7, array-includes@^3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array.prototype.findlast@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.findlastindex@^1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.tosorted@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-shim-unscopables "^1.0.2" + arraybuffer.prototype.slice@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" @@ -2972,7 +3201,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -3078,7 +3307,7 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: dependencies: ms "2.1.2" -debug@^3.1.0: +debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -3105,6 +3334,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.2.2, deepmerge@^4.3.0: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -3139,7 +3373,7 @@ define-lazy-prop@^2.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -3204,6 +3438,20 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + domexception@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" @@ -3265,6 +3513,14 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" +enhanced-resolve@^5.12.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + entities@^4.4.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" @@ -3307,7 +3563,7 @@ errorhandler@^1.5.1: accepts "~1.3.7" escape-html "~1.0.3" -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0: +es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: version "1.23.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== @@ -3371,6 +3627,26 @@ es-errors@^1.2.1, es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-iterator-helpers@^1.0.19: + version "1.0.19" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" + integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + iterator.prototype "^1.1.2" + safe-array-concat "^1.1.2" + es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -3387,6 +3663,13 @@ es-set-tostringtag@^2.0.3: has-tostringtag "^1.0.2" hasown "^2.0.1" +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -3432,12 +3715,207 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" +eslint-config-expo@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/eslint-config-expo/-/eslint-config-expo-7.1.2.tgz#0ecb2c389b013dc3dfbf8a7401269a0589f03156" + integrity sha512-WxrDVNklN43Op0v3fglQfzL2bC7vqacUq9oVwJcGCUEDzdM7kGOR6pfEJiz3i3dQv3cFjHtct0CFEExep5c/dA== + dependencies: + "@typescript-eslint/eslint-plugin" "^7.4.0" + "@typescript-eslint/parser" "^7.4.0" + eslint-import-resolver-typescript "^3.6.1" + eslint-plugin-expo "^0.0.1" + eslint-plugin-import "^2.29.1" + eslint-plugin-react "^7.34.0" + eslint-plugin-react-hooks "^4.6.0" + +eslint-config-prettier@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-import-resolver-typescript@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz#7b983680edd3f1c5bce1a5829ae0bc2d57fe9efa" + integrity sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg== + dependencies: + debug "^4.3.4" + enhanced-resolve "^5.12.0" + eslint-module-utils "^2.7.4" + fast-glob "^3.3.1" + get-tsconfig "^4.5.0" + is-core-module "^2.11.0" + is-glob "^4.0.3" + +eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" + integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== + dependencies: + debug "^3.2.7" + +eslint-plugin-expo@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-expo/-/eslint-plugin-expo-0.0.1.tgz#1164e9593c73619dca26cbc71c58889bd44b4861" + integrity sha512-dNri81vunJ3T+N1YWWxjLU6ux6KiukwZ4ECXCOPp8hG7M4kuvPAb9YQSIM63AT0pbtfYH/a6htikhaQcRPjhRA== + dependencies: + "@typescript-eslint/types" "^7.2.0" + "@typescript-eslint/utils" "^7.2.0" + +eslint-plugin-import@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" + integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.15.0" + +eslint-plugin-prettier@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz#d1c8f972d8f60e414c25465c163d16f209411f95" + integrity sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.9.1" + +eslint-plugin-react-hooks@^4.6.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596" + integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== + +eslint-plugin-react@^7.34.0: + version "7.35.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz#00b1e4559896710e58af6358898f2ff917ea4c41" + integrity sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA== + dependencies: + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" + array.prototype.flatmap "^1.3.2" + array.prototype.tosorted "^1.1.4" + doctrine "^2.1.0" + es-iterator-helpers "^1.0.19" + estraverse "^5.3.0" + hasown "^2.0.2" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.8" + object.fromentries "^2.0.8" + object.values "^1.2.0" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.11" + string.prototype.repeat "^1.0.0" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.57.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -estraverse@^5.2.0: +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== @@ -3589,7 +4067,17 @@ expo@~51.0.14: fbemitter "^3.0.0" whatwg-url-without-unicode "8.0.0-3" -fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.2: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.1, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -3600,11 +4088,16 @@ fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.2: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4: version "4.4.1" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" @@ -3656,6 +4149,13 @@ fetch-retry@^4.1.1: resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-4.1.1.tgz#fafe0bb22b54f4d0a9c788dff6dd7f8673ca63f3" integrity sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -3715,6 +4215,20 @@ find-yarn-workspace-root@~2.0.0: dependencies: micromatch "^4.0.2" +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + flow-enums-runtime@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz#5bb0cd1b0a3e471330f4d109039b7eba5cb3e787" @@ -3898,6 +4412,13 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" +get-tsconfig@^4.5.0: + version "4.7.6" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.6.tgz#118fd5b7b9bae234cc7705a00cd771d7eb65d62a" + integrity sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA== + dependencies: + resolve-pkg-maps "^1.0.0" + getenv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/getenv/-/getenv-1.0.0.tgz#874f2e7544fbca53c7a4738f37de8605c3fcfc31" @@ -3910,6 +4431,13 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob@7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -3962,6 +4490,13 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + globalthis@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" @@ -3970,7 +4505,7 @@ globalthis@^1.0.3: define-properties "^1.2.1" gopd "^1.0.1" -globby@^11.0.1: +globby@^11.0.1, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -3994,6 +4529,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + graphql-tag@^2.10.1: version "2.12.6" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" @@ -4147,7 +4687,7 @@ ieee754@^1.1.13: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0: +ignore@^5.2.0, ignore@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== @@ -4167,6 +4707,14 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-local@^3.0.2: version "3.2.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" @@ -4250,6 +4798,13 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -4275,7 +4830,7 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.13.0: +is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1: version "2.15.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== @@ -4289,7 +4844,7 @@ is-data-view@^1.0.1: dependencies: is-typed-array "^1.1.13" -is-date-object@^1.0.1: +is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== @@ -4316,6 +4871,13 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -4331,6 +4893,13 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -4338,7 +4907,7 @@ is-glob@^2.0.0: dependencies: is-extglob "^1.0.0" -is-glob@^4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -4357,6 +4926,11 @@ is-invalid-path@^0.1.0: dependencies: is-glob "^2.0.0" +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + is-negative-zero@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" @@ -4379,7 +4953,7 @@ is-path-cwd@^2.2.0: resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== -is-path-inside@^3.0.2: +is-path-inside@^3.0.2, is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -4404,6 +4978,11 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" @@ -4454,6 +5033,11 @@ is-valid-path@^0.1.1: dependencies: is-invalid-path "^0.1.0" +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -4461,6 +5045,14 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" @@ -4546,6 +5138,17 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +iterator.prototype@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" + integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== + dependencies: + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" + jackspeak@^3.1.2: version "3.4.3" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" @@ -5085,6 +5688,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -5109,6 +5717,23 @@ json-schema-deref-sync@^0.13.0: traverse "~0.6.6" valid-url "~1.0.9" +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -5130,6 +5755,23 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + 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.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -5145,6 +5787,14 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + lighthouse-logger@^1.0.0: version "1.4.2" resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz#aef90f9e97cd81db367c7634292ee22079280aaa" @@ -5241,6 +5891,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" @@ -5612,7 +6267,7 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1: +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -5860,7 +6515,7 @@ object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.5: +object.assign@^4.1.4, object.assign@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== @@ -5870,6 +6525,43 @@ object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" +object.entries@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +object.fromentries@^2.0.7, object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.values@^1.1.6, object.values@^1.1.7, object.values@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -5934,6 +6626,18 @@ open@^8.0.4, open@^8.3.0: is-docker "^2.1.1" is-wsl "^2.2.0" +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + ora@3.4.0, ora@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" @@ -6036,6 +6740,13 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -6191,6 +6902,23 @@ postcss@~8.4.32: picocolors "^1.0.1" source-map-js "^1.2.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + pretty-bytes@5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -6269,7 +6997,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.1: +punycode@^2.1.0, punycode@^2.1.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -6461,6 +7189,19 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +reflect.getprototypeof@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" + integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.1" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + regenerate-unicode-properties@^10.1.0: version "10.1.1" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" @@ -6558,17 +7299,27 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + resolve.exports@^2.0.0, resolve.exports@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.2: +resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.2, resolve@^1.22.4: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -6577,6 +7328,15 @@ resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.2: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@~1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" @@ -6779,7 +7539,7 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" -set-function-name@^2.0.1: +set-function-name@^2.0.1, set-function-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== @@ -6835,7 +7595,7 @@ shell-quote@^1.6.1, shell-quote@^1.7.3: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== -side-channel@^1.0.4: +side-channel@^1.0.4, side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== @@ -7054,6 +7814,32 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string.prototype.matchall@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" + integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + regexp.prototype.flags "^1.5.2" + set-function-name "^2.0.2" + side-channel "^1.0.6" + +string.prototype.repeat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trim@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" @@ -7124,6 +7910,11 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -7233,6 +8024,19 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +synckit@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.1.tgz#febbfbb6649979450131f64735aa3f6c14575c88" + integrity sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + tar@^6.0.5, tar@^6.1.11: version "6.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" @@ -7406,16 +8210,38 @@ traverse@~0.6.6: typedarray.prototype.slice "^1.0.3" which-typed-array "^1.1.15" +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0: +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -7426,6 +8252,11 @@ type-fest@^0.16.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -7611,6 +8442,13 @@ update-browserslist-db@^1.1.0: escalade "^3.1.2" picocolors "^1.0.1" +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + url-join@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a" @@ -7764,6 +8602,34 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-builtin-type@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.4.tgz#592796260602fc3514a1b5ee7fa29319b72380c3" + integrity sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w== + dependencies: + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.2" + which-typed-array "^1.1.15" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" @@ -7804,6 +8670,11 @@ wonka@^6.3.2: resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.3.4.tgz#76eb9316e3d67d7febf4945202b5bdb2db534594" integrity sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg== +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"