diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts new file mode 100644 index 0000000..0810c94 --- /dev/null +++ b/src/buttonEvents/Effects.ts @@ -0,0 +1,33 @@ +import {ButtonInteraction} from "discord.js"; +import {ButtonEvent} from "../type/buttonEvent"; +import EffectHelper from "../helpers/EffectHelper"; + +export default class Effects extends ButtonEvent { + public override async execute(interaction: ButtonInteraction) { + const action = interaction.customId.split(" ")[1]; + + switch (action) { + case "list": + await this.List(interaction); + break; + } + } + + private async List(interaction: ButtonInteraction) { + const pageOption = interaction.customId.split(" ")[2]; + + const page = Number(pageOption); + + if (!page) { + await interaction.reply("Page option is not a valid number"); + return; + } + + const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page); + + await interaction.update({ + embeds: [ result.embed ], + components: [ result.row ], + }); + } +} diff --git a/src/commands/effects.ts b/src/commands/effects.ts new file mode 100644 index 0000000..bcaa929 --- /dev/null +++ b/src/commands/effects.ts @@ -0,0 +1,45 @@ +import {CommandInteraction, SlashCommandBuilder} from "discord.js"; +import {Command} from "../type/command"; +import EffectHelper from "../helpers/EffectHelper"; + +export default class Effects extends Command { + constructor() { + super(); + + this.CommandBuilder = new SlashCommandBuilder() + .setName("effects") + .setDescription("Effects") + .addSubcommand(x => x + .setName("list") + .setDescription("List all effects I have") + .addNumberOption(x => x + .setName("page") + .setDescription("The page number") + .setMinValue(1))); + } + + public override async execute(interaction: CommandInteraction) { + if (!interaction.isChatInputCommand()) return; + + const subcommand = interaction.options.getSubcommand(); + + switch (subcommand) { + case "list": + await this.List(interaction); + break; + } + } + + private async List(interaction: CommandInteraction) { + const pageOption = interaction.options.get("page"); + + const page = !isNaN(Number(pageOption?.value)) ? Number(pageOption?.value) : 1; + + const result = await EffectHelper.GenerateEffectEmbed(interaction.user.id, page); + + await interaction.reply({ + embeds: [ result.embed ], + components: [ result.row ], + }); + } +} diff --git a/src/database/entities/app/UserEffect.ts b/src/database/entities/app/UserEffect.ts index fa1b584..72fae5f 100644 --- a/src/database/entities/app/UserEffect.ts +++ b/src/database/entities/app/UserEffect.ts @@ -57,4 +57,18 @@ export default class UserEffect extends AppBaseEntity { return single; } + + public static async FetchAllByUserIdPaginated(userId: string, page: number = 0, itemsPerPage: number = 10): Promise<[UserEffect[], number]> { + const repository = AppDataSource.getRepository(UserEffect); + + const query = await repository.createQueryBuilder("effect") + .where("effect.UserId = :userId", { userId }) + .where("effect.Unused > 0") + .orderBy("effect.Name", "ASC") + .skip(page * itemsPerPage) + .take(itemsPerPage) + .getManyAndCount(); + + return query; + } } diff --git a/src/helpers/EffectHelper.ts b/src/helpers/EffectHelper.ts index 14c2f43..d0d29a0 100644 --- a/src/helpers/EffectHelper.ts +++ b/src/helpers/EffectHelper.ts @@ -1,4 +1,6 @@ +import {ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder} from "discord.js"; import UserEffect from "../database/entities/app/UserEffect"; +import EmbedColours from "../constants/EmbedColours"; export default class EffectHelper { public static async AddEffectToUserInventory(userId: string, name: string, quantity: number = 1) { @@ -46,4 +48,49 @@ export default class EffectHelper { return true; } + + public static async GenerateEffectEmbed(userId: string, page: number): Promise<{ + embed: EmbedBuilder, + row: ActionRowBuilder, + }> { + const itemsPerPage = 10; + + const query = await UserEffect.FetchAllByUserIdPaginated(userId, page - 1, itemsPerPage); + + const effects = query[0]; + const count = query[1]; + + const totalPages = count > 0 ? Math.ceil(count / itemsPerPage) : 1; + + let description = "*none*"; + + if (effects.length > 0) { + description = effects.map(x => `${x.Name} x${x.Unused}`).join("\n"); + } + + const embed = new EmbedBuilder() + .setTitle("Effects") + .setDescription(description) + .setColor(EmbedColours.Ok) + .setFooter({ text: `Page ${page} of ${totalPages}` }); + + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(`effects list ${page - 1}`) + .setLabel("Previous") + .setStyle(ButtonStyle.Primary) + .setDisabled(page == 1), + new ButtonBuilder() + .setCustomId(`effects list ${page + 1}`) + .setLabel("Next") + .setStyle(ButtonStyle.Primary) + .setDisabled(page == totalPages), + ); + + return { + embed, + row, + }; + } } diff --git a/src/registry.ts b/src/registry.ts index e4e5d64..3ae885d 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -7,6 +7,7 @@ import AllBalance from "./commands/allbalance"; import Balance from "./commands/balance"; import Daily from "./commands/daily"; import Drop from "./commands/drop"; +import Effects from "./commands/effects"; import Gdrivesync from "./commands/gdrivesync"; import Give from "./commands/give"; import Id from "./commands/id"; @@ -25,6 +26,7 @@ import Droprarity from "./commands/stage/droprarity"; // Button Event Imports import Claim from "./buttonEvents/Claim"; +import EffectsButtonEvent from "./buttonEvents/Effects"; import InventoryButtonEvent from "./buttonEvents/Inventory"; import MultidropButtonEvent from "./buttonEvents/Multidrop"; import Reroll from "./buttonEvents/Reroll"; @@ -44,6 +46,7 @@ export default class Registry { CoreClient.RegisterCommand("balance", new Balance()); CoreClient.RegisterCommand("daily", new Daily()); CoreClient.RegisterCommand("drop", new Drop()); + CoreClient.RegisterCommand("effects", new Effects()); CoreClient.RegisterCommand("gdrivesync", new Gdrivesync()); CoreClient.RegisterCommand("give", new Give()); CoreClient.RegisterCommand("id", new Id()); @@ -63,6 +66,7 @@ export default class Registry { public static RegisterButtonEvents() { CoreClient.RegisterButtonEvent("claim", new Claim()); + CoreClient.RegisterButtonEvent("effects", new EffectsButtonEvent()); CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent()); CoreClient.RegisterButtonEvent("multidrop", new MultidropButtonEvent()); CoreClient.RegisterButtonEvent("reroll", new Reroll()); diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts new file mode 100644 index 0000000..557e64a --- /dev/null +++ b/tests/buttonEvents/Effects.test.ts @@ -0,0 +1,127 @@ +import {ButtonInteraction} from "discord.js"; +import Effects from "../../src/buttonEvents/Effects"; +import EffectHelper from "../../src/helpers/EffectHelper"; + +describe("execute", () => { + describe("GIVEN action in custom id is list", () => { + const interaction = { + customId: "effects list", + } as unknown as ButtonInteraction; + + let listSpy: jest.SpyInstance; + + beforeAll(async () => { + const effects = new Effects(); + + listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List") + .mockImplementation(); + + await effects.execute(interaction); + }); + + test("EXPECT list function to be called", () => { + expect(listSpy).toHaveBeenCalledTimes(1); + expect(listSpy).toHaveBeenCalledWith(interaction); + }); + }); +}); + +describe("List", () => { + let interaction: ButtonInteraction; + + const embed = { + name: "Embed", + }; + + const row = { + name: "Row", + }; + + beforeEach(() => { + interaction = { + customId: "effects list", + user: { + id: "userId", + }, + update: jest.fn(), + reply: jest.fn(), + } as unknown as ButtonInteraction; + }); + + describe("GIVEN page is a valid number", () => { + beforeEach(async () => { + interaction.customId += " 1"; + + EffectHelper.GenerateEffectEmbed = jest.fn() + .mockResolvedValue({ + embed, + row, + }); + + const effects = new Effects(); + + await effects.execute(interaction); + }); + + test("EXPECT EffectHelper.GenerateEffectEmbed to be called", () => { + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); + }); + + test("EXPECT interaction to be updated", () => { + expect(interaction.update).toHaveBeenCalledTimes(1); + expect(interaction.update).toHaveBeenCalledWith({ + embeds: [ embed ], + components: [ row ], + }); + }); + }); + + describe("GIVEN page in custom id is not supplied", () => { + beforeEach(async () => { + EffectHelper.GenerateEffectEmbed = jest.fn() + .mockResolvedValue({ + embed, + row, + }); + + const effects = new Effects(); + + await effects.execute(interaction); + }); + + test("EXPECT interaction to be replied with error", () => { + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number"); + }); + + test("EXPECT interaction to not be updated", () => { + expect(interaction.update).not.toHaveBeenCalled(); + }); + }); + + describe("GIVEN page in custom id is not a number", () => { + beforeEach(async () => { + interaction.customId += " test"; + + EffectHelper.GenerateEffectEmbed = jest.fn() + .mockResolvedValue({ + embed, + row, + }); + + const effects = new Effects(); + + await effects.execute(interaction); + }); + + test("EXPECT interaction to be replied with error", () => { + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith("Page option is not a valid number"); + }); + + test("EXPECT interaction to not be updated", () => { + expect(interaction.update).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/tests/commands/__snapshots__/effects.test.ts.snap b/tests/commands/__snapshots__/effects.test.ts.snap new file mode 100644 index 0000000..ede2091 --- /dev/null +++ b/tests/commands/__snapshots__/effects.test.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`constructor EXPECT CommandBuilder to be defined 1`] = ` +{ + "contexts": undefined, + "default_member_permissions": undefined, + "default_permission": undefined, + "description": "Effects", + "description_localizations": undefined, + "dm_permission": undefined, + "integration_types": undefined, + "name": "effects", + "name_localizations": undefined, + "nsfw": undefined, + "options": [ + { + "description": "List all effects I have", + "description_localizations": undefined, + "name": "list", + "name_localizations": undefined, + "options": [ + { + "autocomplete": undefined, + "choices": undefined, + "description": "The page number", + "description_localizations": undefined, + "max_value": undefined, + "min_value": 1, + "name": "page", + "name_localizations": undefined, + "required": false, + "type": 10, + }, + ], + "type": 1, + }, + ], + "type": 1, +} +`; diff --git a/tests/commands/effects.test.ts b/tests/commands/effects.test.ts new file mode 100644 index 0000000..8985477 --- /dev/null +++ b/tests/commands/effects.test.ts @@ -0,0 +1,164 @@ +import {ChatInputCommandInteraction} from "discord.js"; +import Effects from "../../src/commands/effects"; +import EffectHelper from "../../src/helpers/EffectHelper"; + +describe("constructor", () => { + let effects: Effects; + + beforeEach(() => { + effects = new Effects(); + }); + + test("EXPECT CommandBuilder to be defined", () => { + expect(effects.CommandBuilder).toMatchSnapshot(); + }); +}); + +describe("execute", () => { + describe("GIVEN interaction is not a chat input command", () => { + let interaction: ChatInputCommandInteraction; + + let listSpy: jest.SpyInstance; + + beforeEach(async () => { + interaction = { + isChatInputCommand: jest.fn().mockReturnValue(false), + } as unknown as ChatInputCommandInteraction; + + const effects = new Effects(); + + listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List") + .mockImplementation(); + + await effects.execute(interaction); + }); + + test("EXPECT isChatInputCommand to have been called", () => { + expect(interaction.isChatInputCommand).toHaveBeenCalledTimes(1); + }); + + test("EXPECT nothing to happen", () => { + expect(listSpy).not.toHaveBeenCalled(); + }); + }); + + describe("GIVEN subcommand is list", () => { + let interaction: ChatInputCommandInteraction; + + let listSpy: jest.SpyInstance; + + beforeEach(async () => { + interaction = { + isChatInputCommand: jest.fn().mockReturnValue(true), + options: { + getSubcommand: jest.fn().mockReturnValue("list"), + }, + } as unknown as ChatInputCommandInteraction; + + const effects = new Effects(); + + listSpy = jest.spyOn(effects as unknown as {"List": () => object}, "List") + .mockImplementation(); + + await effects.execute(interaction); + }); + + test("EXPECT subcommand function to be called", () => { + expect(interaction.options.getSubcommand).toHaveBeenCalledTimes(1); + }); + + test("EXPECT list function to be called", () => { + expect(listSpy).toHaveBeenCalledTimes(1); + expect(listSpy).toHaveBeenCalledWith(interaction); + }); + }); +}); + +describe("List", () => { + const effects: Effects = new Effects(); + let interaction: ChatInputCommandInteraction; + + const embed = { + name: "embed", + }; + + const row = { + name: "row", + }; + + beforeEach(async () => { + interaction = { + isChatInputCommand: jest.fn().mockReturnValue(true), + options: { + getSubcommand: jest.fn().mockReturnValue("list"), + }, + reply: jest.fn(), + user: { + id: "userId", + }, + } as unknown as ChatInputCommandInteraction; + + const effects = new Effects(); + + EffectHelper.GenerateEffectEmbed = jest.fn().mockReturnValue({ + embed, + row, + }); + + jest.spyOn(effects as unknown as {"List": () => object}, "List") + .mockImplementation(); + }); + + describe("GIVEN page option is supplied", () => { + describe("AND page is a valid number", () => { + beforeEach(async () => { + interaction.options.get = jest.fn().mockReturnValueOnce({ + value: "2", + }); + + await effects.execute(interaction); + }); + + test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page", () => { + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 2); + }); + + test("EXPECT interaction to have been replied", () => { + expect(interaction.reply).toHaveBeenCalledTimes(1); + expect(interaction.reply).toHaveBeenCalledWith({ + embeds: [ embed ], + components: [ row ], + }); + }); + }); + + describe("AND page is not a valid number", () => { + beforeEach(async () => { + interaction.options.get = jest.fn().mockReturnValueOnce({ + value: "test", + }); + + await effects.execute(interaction); + }); + + test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1", () => { + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); + }); + }); + }); + + describe("GIVEN page option is not supplied", () => { + beforeEach(async () => { + interaction.options.get = jest.fn().mockReturnValueOnce(undefined); + + await effects.execute(interaction); + }); + + test("EXPECT EffectHelper.GenerateEffectEmbed to have been called with page of 1", () => { + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledTimes(1); + expect(EffectHelper.GenerateEffectEmbed).toHaveBeenCalledWith("userId", 1); + }); + }); +}); diff --git a/tests/helpers/EffectHelper.test.ts b/tests/helpers/EffectHelper.test.ts index 343f06c..13dca37 100644 --- a/tests/helpers/EffectHelper.test.ts +++ b/tests/helpers/EffectHelper.test.ts @@ -1,3 +1,4 @@ +import {ActionRowBuilder, ButtonBuilder, EmbedBuilder} from "discord.js"; import UserEffect from "../../src/database/entities/app/UserEffect"; import EffectHelper from "../../src/helpers/EffectHelper"; @@ -279,3 +280,101 @@ describe("HasEffect", () => { }); }); }); + +describe("GenerateEffectEmbed", () => { + beforeEach(async () => { + UserEffect.FetchAllByUserIdPaginated = jest.fn() + .mockResolvedValue([ + [], + 0, + ]); + + await EffectHelper.GenerateEffectEmbed("userId", 1); + }); + + test("EXPECT UserEffect.FetchAllByUserIdPaginated to be called", () => { + expect(UserEffect.FetchAllByUserIdPaginated).toHaveBeenCalledTimes(1); + expect(UserEffect.FetchAllByUserIdPaginated).toHaveBeenCalledWith("userId", 0, 10); + }); + + describe("GIVEN there are no effects returned", () => { + let result: { + embed: EmbedBuilder, + row: ActionRowBuilder, + }; + + beforeEach(async () => { + UserEffect.FetchAllByUserIdPaginated = jest.fn() + .mockResolvedValue([ + [], + 0, + ]); + + result = await EffectHelper.GenerateEffectEmbed("userId", 1); + }); + + test("EXPECT result returned", () => { + expect(result).toMatchSnapshot(); + }); + }); + + describe("GIVEN there are effects returned", () => { + let result: { + embed: EmbedBuilder, + row: ActionRowBuilder, + }; + + beforeEach(async () => { + UserEffect.FetchAllByUserIdPaginated = jest.fn() + .mockResolvedValue([ + [ + { + Name: "name", + Unused: 1, + }, + ], + 1, + ]); + + result = await EffectHelper.GenerateEffectEmbed("userId", 1); + }); + + test("EXPECT result returned", () => { + expect(result).toMatchSnapshot(); + }); + + describe("AND it is the first page", () => { + beforeEach(async () => { + result = await EffectHelper.GenerateEffectEmbed("userId", 1) + }); + + test("EXPECT Previous button to be disabled", () => { + const button = result.row.components[0].data as unknown as { + label: string, + disabled: boolean + }; + + expect(button).toBeDefined(); + expect(button.label).toBe("Previous"); + expect(button.disabled).toBe(true); + }); + }); + + describe("AND it is the last page", () => { + beforeEach(async () => { + result = await EffectHelper.GenerateEffectEmbed("userId", 1) + }); + + test("EXPECT Next button to be disabled", () => { + const button = result.row.components[1].data as unknown as { + label: string, + disabled: boolean + }; + + expect(button).toBeDefined(); + expect(button.label).toBe("Next"); + expect(button.disabled).toBe(true); + }); + }); + }); +}); diff --git a/tests/helpers/__snapshots__/EffectHelper.test.ts.snap b/tests/helpers/__snapshots__/EffectHelper.test.ts.snap new file mode 100644 index 0000000..6484acd --- /dev/null +++ b/tests/helpers/__snapshots__/EffectHelper.test.ts.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GenerateEffectEmbed GIVEN there are effects returned EXPECT result returned 1`] = ` +{ + "embed": { + "color": 3166394, + "description": "name x1", + "footer": { + "icon_url": undefined, + "text": "Page 1 of 1", + }, + "title": "Effects", + }, + "row": { + "components": [ + { + "custom_id": "effects list 0", + "disabled": true, + "emoji": undefined, + "label": "Previous", + "style": 1, + "type": 2, + }, + { + "custom_id": "effects list 2", + "disabled": true, + "emoji": undefined, + "label": "Next", + "style": 1, + "type": 2, + }, + ], + "type": 1, + }, +} +`; + +exports[`GenerateEffectEmbed GIVEN there are no effects returned EXPECT result returned 1`] = ` +{ + "embed": { + "color": 3166394, + "description": "*none*", + "footer": { + "icon_url": undefined, + "text": "Page 1 of 1", + }, + "title": "Effects", + }, + "row": { + "components": [ + { + "custom_id": "effects list 0", + "disabled": true, + "emoji": undefined, + "label": "Previous", + "style": 1, + "type": 2, + }, + { + "custom_id": "effects list 2", + "disabled": true, + "emoji": undefined, + "label": "Next", + "style": 1, + "type": 2, + }, + ], + "type": 1, + }, +} +`; diff --git a/yarn.lock b/yarn.lock index 0313723..7bcad77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1304,7 +1304,7 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@8.11.0", "@typescript-eslint/parser@^8.11.0": +"@typescript-eslint/parser@8.11.0": version "8.11.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.11.0.tgz#2ad1481388dc1c937f50b2d138c9ca57cc6c5cce" integrity sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg== @@ -1315,6 +1315,17 @@ "@typescript-eslint/visitor-keys" "8.11.0" debug "^4.3.4" +"@typescript-eslint/parser@^8.11.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.17.0.tgz#2ee972bb12fa69ac625b85813dc8d9a5a053ff52" + integrity sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg== + dependencies: + "@typescript-eslint/scope-manager" "8.17.0" + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/typescript-estree" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" + debug "^4.3.4" + "@typescript-eslint/scope-manager@8.11.0": version "8.11.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz#9d399ce624118966732824878bc9a83593a30405" @@ -1323,6 +1334,14 @@ "@typescript-eslint/types" "8.11.0" "@typescript-eslint/visitor-keys" "8.11.0" +"@typescript-eslint/scope-manager@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz#a3f49bf3d4d27ff8d6b2ea099ba465ef4dbcaa3a" + integrity sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg== + dependencies: + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" + "@typescript-eslint/type-utils@8.11.0": version "8.11.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz#b7f9e6120c1ddee8a1a07615646642ad85fc91b5" @@ -1338,6 +1357,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.11.0.tgz#7c766250502097f49bbc2e651132e6bf489e20b8" integrity sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw== +"@typescript-eslint/types@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.17.0.tgz#ef84c709ef8324e766878834970bea9a7e3b72cf" + integrity sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA== + "@typescript-eslint/typescript-estree@8.11.0": version "8.11.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz#35fe5d3636fc5727c52429393415412e552e222b" @@ -1352,6 +1376,20 @@ semver "^7.6.0" ts-api-utils "^1.3.0" +"@typescript-eslint/typescript-estree@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz#40b5903bc929b1e8dd9c77db3cb52cfb199a2a34" + integrity sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw== + dependencies: + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + "@typescript-eslint/utils@8.11.0": version "8.11.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.11.0.tgz#4480d1e9f2bb18ea3510c79f870a1aefc118103d" @@ -1370,6 +1408,14 @@ "@typescript-eslint/types" "8.11.0" eslint-visitor-keys "^3.4.3" +"@typescript-eslint/visitor-keys@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz#4dbcd0e28b9bf951f4293805bf34f98df45e1aa8" + integrity sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg== + dependencies: + "@typescript-eslint/types" "8.17.0" + eslint-visitor-keys "^4.2.0" + "@vladfrangu/async_event_emitter@^2.2.4", "@vladfrangu/async_event_emitter@^2.4.6": version "2.4.6" resolved "https://registry.yarnpkg.com/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz#508b6c45b03f917112a9008180b308ba0e4d1805" @@ -2209,13 +2255,20 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: ms "^2.1.3" +debug@^4.3.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + decompress-response@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" @@ -2512,6 +2565,11 @@ eslint-visitor-keys@^4.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz#1f785cc5e81eb7534523d85922248232077d2f8c" integrity sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg== +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + eslint@^9.13.0: version "9.13.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.13.0.tgz#7659014b7dda1ff876ecbd990f726e11c61596e6" @@ -5926,9 +5984,9 @@ triple-beam@^1.2.0, triple-beam@^1.3.0, triple-beam@^1.4.1: integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== 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== + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== ts-essentials@^10.0.0: version "10.0.2"