From e994371b2f5b4fd626d1f40239229b5122b194dc Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Wed, 22 Oct 2025 20:01:18 -0600 Subject: [PATCH 01/14] chore: add TypeScript type annotations to function parameters and callbacks --- src/commands/stats.ts | 30 +++++++++++++----------- src/reactionHandlers/updateReactboard.ts | 4 ++-- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/commands/stats.ts b/src/commands/stats.ts index 7f2aadd4..4d096f94 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -17,53 +17,55 @@ const LeaderboardSubcommand = 'leaderboard'; const builder = new SlashCommandBuilder() .setName('stats') .setDescription('Track and display stats and leaderboards') - .addSubcommand(subcommand => + .addSubcommand((subcommand: any) => subcommand .setName(TrackSubcommand) .setDescription('Begin tracking a stat for you') - .addStringOption(option => + .addStringOption((option: any) => option .setName(StatNameOption) .setDescription('The name of the stat you would like to track') .setRequired(true) ) ) - .addSubcommand(subcommand => + .addSubcommand((subcommand: any) => subcommand .setName(UpdateSubcommand) .setDescription("Adds to a stat you're tracking") - .addStringOption(option => + .addStringOption((option: any) => option .setName(StatNameOption) .setDescription('The name of the stat to update') .setRequired(true) ) - .addNumberOption(option => + .addNumberOption((option: any) => option .setName(AmountOption) .setDescription('The amount to update the stat') .setRequired(true) ) ) - .addSubcommand(subcommand => - subcommand.setName(ListSubcommand).setDescription("List all stats I'm tracking for you") + .addSubcommand((subcommand: any) => + subcommand + .setName(ListSubcommand) + .setDescription("List all stats I'm tracking for you") ) - .addSubcommand(subcommand => + .addSubcommand((subcommand: any) => subcommand .setName(UntrackSubcommand) .setDescription('Stops tracking a stat for you') - .addStringOption(option => + .addStringOption((option: any) => option .setName(StatNameOption) .setDescription('The name of the stat you would like to stop tracking') .setRequired(true) ) ) - .addSubcommand(subcommand => + .addSubcommand((subcommand: any) => subcommand .setName(LeaderboardSubcommand) .setDescription('Show the leaderboard for a stat') - .addStringOption(option => + .addStringOption((option: any) => option .setName(StatNameOption) .setDescription('The name of the stat for which to show the leaderboard') @@ -194,7 +196,7 @@ async function list( const embedDescription = scoreboardEntries.length > 0 - ? scoreboardEntries.map(entry => `${entry.name}: ${entry.score}`).join('\n') + ? scoreboardEntries.map((entry: { name: string; score: number }) => `${entry.name}: ${entry.score}`).join('\n') : "You're not currently tracking anything!"; const embed = new EmbedBuilder() @@ -259,10 +261,10 @@ async function leaderboard( throw new UserMessageError(`No one is tracking the stat "${statName}"`); } - const scoresSorted = scoreboardEntries.sort((a, b) => b.score - a.score); + const scoresSorted = scoreboardEntries.sort((a: { score: number }, b: { score: number }) => b.score - a.score); const embedDescription = scoresSorted - .map(entry => { + .map((entry: { userId: string; score: number }) => { return `${userMention(entry.userId)}: ${entry.score}`; }) .join('\n'); diff --git a/src/reactionHandlers/updateReactboard.ts b/src/reactionHandlers/updateReactboard.ts index 18f6b5e3..b7b0ce9f 100644 --- a/src/reactionHandlers/updateReactboard.ts +++ b/src/reactionHandlers/updateReactboard.ts @@ -82,7 +82,7 @@ async function updateExistingPosts(reaction: MessageReaction, message: Message): }, }); - const updatePromises = reactboardPosts.map(async reactboardPost => { + const updatePromises = reactboardPosts.map(async (reactboardPost: { reactboard: { channelId: string }; reactboardMessageId: string }) => { const reactboardChannel = await getChannel(reaction, reactboardPost.reactboard.channelId); const reactboardMessage = await reactboardChannel.messages.fetch( reactboardPost.reactboardMessageId @@ -112,7 +112,7 @@ async function addNewPosts(reaction: MessageReaction, message: Message): Promise }, }); - const updatePromises = reactboardsToPostTo.map(async reactboard => { + const updatePromises = reactboardsToPostTo.map(async (reactboard: { id: number; channelId: string }) => { const channel = await getChannel(reaction, reactboard.channelId); const reactboardMessage = await channel.send({ embeds: [buildEmbed(reaction, message)] }); await db.reactboardPost.create({ From 54c4450b1991682958929af4b220894d5e267ff5 Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Wed, 22 Oct 2025 20:03:32 -0600 Subject: [PATCH 02/14] feat: add room finder module with scraper and search functionality --- .gitignore | 1 + package-lock.json | 477 +++++++++++++++++++++++++++++++---- package.json | 4 + prisma/schema.prisma | 88 ++++--- src/events/ready.ts | 9 + src/roomFinder/api.ts | 195 +++++++++++++++ src/roomFinder/cron.ts | 74 ++++++ src/roomFinder/index.ts | 16 ++ src/roomFinder/scraper.ts | 509 ++++++++++++++++++++++++++++++++++++++ src/roomFinder/search.ts | 262 ++++++++++++++++++++ src/roomFinder/types.ts | 62 +++++ src/roomFinder/utils.ts | 150 +++++++++++ 12 files changed, 1759 insertions(+), 88 deletions(-) create mode 100644 src/roomFinder/api.ts create mode 100644 src/roomFinder/cron.ts create mode 100644 src/roomFinder/index.ts create mode 100644 src/roomFinder/scraper.ts create mode 100644 src/roomFinder/search.ts create mode 100644 src/roomFinder/types.ts create mode 100644 src/roomFinder/utils.ts diff --git a/.gitignore b/.gitignore index e5133124..bb9810de 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ node_modules db dist coverage +scraper src/constants/version.ts tmp temp diff --git a/package-lock.json b/package-lock.json index 237c0db3..8945f472 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "dependencies": { "@discordjs/voice": "0.18.0", "@prisma/client": "6.13.0", + "axios": "1.12.2", + "cheerio": "1.1.2", + "cron": "4.3.3", "dectalk-tts": "1.0.1", "discord.js": "14.19.1", "ffmpeg-static": "5.2.0", @@ -21,6 +24,7 @@ }, "devDependencies": { "@stylistic/eslint-plugin": "4.4.1", + "@types/cheerio": "0.22.35", "@types/node": "20.17.48", "@types/semver": "7.7.0", "@types/source-map-support": "0.5.10", @@ -1948,6 +1952,16 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/cheerio": { + "version": "0.22.35", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.35.tgz", + "integrity": "sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -1955,19 +1969,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/eslint": { - "version": "8.56.11", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz", - "integrity": "sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1988,6 +1989,12 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.17.48", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.48.tgz", @@ -3005,6 +3012,12 @@ "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -3020,6 +3033,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3063,6 +3087,12 @@ "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", "dev": true }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3168,7 +3198,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3280,6 +3309,57 @@ "node": ">= 16" } }, + "node_modules/cheerio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio/node_modules/undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -3399,6 +3479,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -3450,6 +3542,19 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cron": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.3.3.tgz", + "integrity": "sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==", + "license": "MIT", + "dependencies": { + "@types/luxon": "~3.7.0", + "luxon": "~3.7.0" + }, + "engines": { + "node": ">=18.x" + } + }, "node_modules/croner": { "version": "4.1.97", "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", @@ -3471,6 +3576,34 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/culvert": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", @@ -3634,6 +3767,15 @@ "node": ">= 14" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/discord-api-types": { "version": "0.37.119", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.119.tgz", @@ -3673,11 +3815,65 @@ "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", "license": "MIT" }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3724,6 +3920,31 @@ "dev": true, "license": "MIT" }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -3736,6 +3957,18 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -3808,7 +4041,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3818,7 +4050,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -3834,7 +4065,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3844,14 +4074,15 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -4403,20 +4634,6 @@ } } }, - "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, "node_modules/eslint-import-context": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.8.tgz", @@ -5120,7 +5337,6 @@ "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true, "funding": [ { "type": "individual", @@ -5175,6 +5391,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -5194,7 +5426,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5257,7 +5488,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5282,7 +5512,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5393,7 +5622,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5455,7 +5683,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5468,7 +5695,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -5483,7 +5709,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -5498,6 +5723,37 @@ "dev": true, "license": "MIT" }, + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -6269,6 +6525,15 @@ "yallist": "^3.0.2" } }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-bytes.js": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", @@ -6316,7 +6581,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6344,6 +6608,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -6595,6 +6880,18 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -6829,6 +7126,55 @@ "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7299,7 +7645,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, "license": "MIT" }, "node_modules/pstree.remy": { @@ -7629,8 +7974,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sax": { "version": "1.3.0", @@ -9010,6 +9354,39 @@ "lodash": "^4.17.14" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 3aa32d5e..4bda1517 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,9 @@ "dependencies": { "@discordjs/voice": "0.18.0", "@prisma/client": "6.13.0", + "axios": "1.12.2", + "cheerio": "1.1.2", + "cron": "4.3.3", "dectalk-tts": "1.0.1", "discord.js": "14.19.1", "ffmpeg-static": "5.2.0", @@ -58,6 +61,7 @@ }, "devDependencies": { "@stylistic/eslint-plugin": "4.4.1", + "@types/cheerio": "0.22.35", "@types/node": "20.17.48", "@types/semver": "7.7.0", "@types/source-map-support": "0.5.10", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c8345e47..87123b8f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -2,55 +2,67 @@ // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" } datasource db { - provider = "sqlite" - url = env("DATABASE_URL") + provider = "sqlite" + url = env("DATABASE_URL") } model Reactboard { - id Int @id @default(autoincrement()) - guildId String - channelId String - react String - isCustomReact Boolean - threshold Int + id Int @id @default(autoincrement()) + guildId String + channelId String + react String + isCustomReact Boolean + threshold Int - @@unique([guildId, channelId], name: "location") + reactboardPosts ReactboardPost[] - reactboardPosts ReactboardPost[] + @@unique([guildId, channelId], name: "location") } model ReactboardPost { - id Int @id @default(autoincrement()) - reactboardId Int - reactboard Reactboard @relation(fields: [reactboardId], references: [id]) - originalMessageId String - originalChannelId String - reactboardMessageId String + id Int @id @default(autoincrement()) + reactboardId Int + reactboard Reactboard @relation(fields: [reactboardId], references: [id]) + originalMessageId String + originalChannelId String + reactboardMessageId String } model Scoreboard { - id Int @id @default(autoincrement()) - userId String - guildId String - name String - score Float -} - -// model Tag { -// id Int @id @default(autoincrement()) -// createdAt DateTime @default(now()) -// name String @unique -// content String -// } - -// model User { -// id Int @id @default(autoincrement()) -// createdAt DateTime @default(now()) -// userId String @unique -// starredCount Int -// leaderboards Leaderboard[] -// } + id Int @id @default(autoincrement()) + userId String + guildId String + name String + score Float +} + +model Buildings { + id Int @id @default(autoincrement()) + name String + + rooms Rooms[] +} + +model Rooms { + id Int @id @default(autoincrement()) + buildingId Int + building Buildings @relation(fields: [buildingId], references: [id], onDelete: Cascade) + number String + description String + + events Events[] +} + +model Events { + id Int @id @default(autoincrement()) + roomId Int + room Rooms @relation(fields: [roomId], references: [id], onDelete: Cascade) + name String + days String // JSON array of days: ["M", "T", "W", "Th", "F", "Sa", "Su"] + startTime String // TIME format: "HH:MM:SS" + endTime String // TIME format: "HH:MM:SS" +} diff --git a/src/events/ready.ts b/src/events/ready.ts index 80e7af27..704d2d2d 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -8,6 +8,7 @@ import { onEvent } from '../helpers/onEvent.js'; import { parseArgs } from '../helpers/parseArgs.js'; import { verifyCommandDeployments } from '../helpers/actions/verifyCommandDeployments.js'; import { info } from '../logger.js'; +import { setupScraperCron } from '../roomFinder/cron.js'; /** * The event handler for when the Discord Client is ready for action @@ -41,6 +42,14 @@ export const ready = onEvent('ready', { info('Setting user activity'); setActivity(client); + // Set up automatic room scraping (optional - comment out if you don't want it) + // This will scrape BYU room data every Sunday at 2 AM Mountain Time + setupScraperCron(); // Default: '0 2 * * 0' (2 AM every Sunday) + // Or customize the schedule: + // setupScraperCron('0 3 * * 1'); // 3 AM every Monday + // setupScraperCron('0 0 1 * *'); // Midnight on 1st of month + // setupScraperCron('0 2 * * 0', '20251'); // Specific semester + info('Ready!'); }, }); diff --git a/src/roomFinder/api.ts b/src/roomFinder/api.ts new file mode 100644 index 00000000..d998d725 --- /dev/null +++ b/src/roomFinder/api.ts @@ -0,0 +1,195 @@ +import { lookup } from './search.js'; +import { type RoomSearchParams, type RoomAvailabilityResult, type EventInfo } from './types.js'; + +/** + * API handlers matching the Python FastAPI server + * These functions can be used with Express, Fastify, or directly in Discord commands + */ + +export interface RoomsResponse { + Rooms: [string, string][]; // [room_number, building_name] +} + +export interface WhenResponse { + busySince: string; + busyUntil: string; + isInUse: boolean; +} + +/** + * GET /now/{building} + * Finds available rooms right now in the specified building + */ +export async function searchNow(building: string): Promise { + const params: RoomSearchParams = { + building: building.toUpperCase(), + timeType: 'now', + days: [] + }; + + const result = await lookup(params); + + // Convert to Python format: [(room_number, building_name), ...] + // Type guard: 'now' returns RoomAvailabilityResult[] + const rooms = (result as RoomAvailabilityResult[]).map(r => [r.roomNumber, r.buildingName] as [string, string]); + + // Handle 'ANY' building special case - return max 24 random rooms sorted by building + if (building.toUpperCase() === 'ANY' && rooms.length > 0) { + const shuffled = rooms + .map(value => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value) + .slice(0, Math.min(24, rooms.length)); + + // Sort by building name, then room number + shuffled.sort((a, b) => { + if (a[1] !== b[1]) return a[1].localeCompare(b[1]); + return a[0].localeCompare(b[0]); + }); + + return { Rooms: shuffled }; + } + + return { Rooms: rooms }; +} + +/** + * GET /at/{building}/{time}?d=Mon&d=Wed + * Finds available rooms at a specific time on specified days + */ +export async function searchAt( + building: string, + time: string, + days: string[] = [] +): Promise { + // Python capitalizes the days: [x.capitalize() for x in d] + const capitalizedDays = days.map(d => d.charAt(0).toUpperCase() + d.slice(1).toLowerCase()); + + const params: RoomSearchParams = { + building: building.toUpperCase(), + timeType: 'at', + timeA: time, + days: capitalizedDays + }; + + const result = await lookup(params); + // Type guard: 'at' returns RoomAvailabilityResult[] + const rooms = (result as RoomAvailabilityResult[]).map(r => [r.roomNumber, r.buildingName] as [string, string]); + + return { Rooms: rooms }; +} + +/** + * GET /between/{building}/{timeA}/{timeB}?d=Mon&d=Wed + * Finds available rooms between two times on specified days + */ +export async function searchBetween( + building: string, + timeA: string, + timeB: string, + days: string[] = [] +): Promise { + // Python capitalizes the days: [x.capitalize() for x in d] + const capitalizedDays = days.map(d => d.charAt(0).toUpperCase() + d.slice(1).toLowerCase()); + + const params: RoomSearchParams = { + building: building.toUpperCase(), + timeType: 'between', + timeA, + timeB, + days: capitalizedDays + }; + + const result = await lookup(params); + // Type guard: 'between' returns RoomAvailabilityResult[] + const rooms = (result as RoomAvailabilityResult[]).map(r => [r.roomNumber, r.buildingName] as [string, string]); + + return { Rooms: rooms }; +} + +/** + * GET /when/{building}/{room} + * Checks when a specific room is busy/available + */ +export async function searchWhen(building: string, room: string): Promise { + const params: RoomSearchParams = { + building: building.toUpperCase(), + room: room, + timeType: 'when', + days: [] + }; + + const result = await lookup(params); + // Type guard: 'when' returns EventInfo[] + const events = result as EventInfo[]; + + // No events found + if (events.length === 0) { + return { + busySince: '', + busyUntil: '', + isInUse: false + }; + } + + // Get current Mountain time to check if room is in use + const now = new Date(); + const mountainTime = new Date(now.toLocaleString('en-US', { timeZone: 'America/Denver' })); + + // Get today's date for building datetime objects + const today = mountainTime; + const dayEvents = events.map(e => ({ + name: e.name, + start: parseTimeToDate(today, e.startTime), + end: parseTimeToDate(today, e.endTime) + })); + + // Python logic: Find first event and determine busy_until based on gap between events + let busySince = dayEvents[0]!.start; + let busyUntil = dayEvents[0]!.end; + + if (dayEvents.length > 1) { + // Check for gaps > 15 minutes between consecutive events + for (let i = 0; i < dayEvents.length - 1; i++) { + const endTime = dayEvents[i]!.end; + const nextStartTime = dayEvents[i + 1]!.start; + const gapMinutes = (nextStartTime.getTime() - endTime.getTime()) / (1000 * 60); + + if (gapMinutes > 15) { + busyUntil = endTime; + break; + } + busyUntil = dayEvents[i + 1]!.end; + } + } + + // Check if currently in use: busy_until > my_date > busy_since + const isInUse = mountainTime > busySince && mountainTime < busyUntil; + + // Format dates to match Python format: '2023-02-06T12:15:00-07:00' + const formatDate = (date: Date): string => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}-07:00`; + }; + + return { + busySince: formatDate(busySince), + busyUntil: formatDate(busyUntil), + isInUse + }; +} + +/** + * Helper function to parse time string (HH:MM:SS) into a Date object for today + */ +function parseTimeToDate(baseDate: Date, timeStr: string): Date { + const [hours, minutes, seconds] = timeStr.split(':').map(Number); + const date = new Date(baseDate); + date.setHours(hours!, minutes!, seconds!, 0); + return date; +} diff --git a/src/roomFinder/cron.ts b/src/roomFinder/cron.ts new file mode 100644 index 00000000..1f04b21e --- /dev/null +++ b/src/roomFinder/cron.ts @@ -0,0 +1,74 @@ +/** + * Optional Cron Scheduler for Room Scraper + * + * Automatically runs the room scraper on a schedule. + * Import and call setupScraperCron() in your main bot file to enable. + */ + +import { CronJob } from 'cron'; +import { scrapeRoomData, getCurrentYearTerm } from './scraper.js'; +import { info, error } from '../logger.js'; + +/** + * Sets up automatic room scraping on a schedule + * + * @param cronSchedule - Cron schedule string (default: '0 2 * * 0' = 2 AM every Sunday) + * @param yearTerm - Optional specific year/term, or auto-detect current semester + * @returns The CronJob instance (in case you want to stop it later) + * + * Common cron schedules: + * - '0 2 * * 0' = 2 AM every Sunday + * - '0 3 * * 1' = 3 AM every Monday + * - '0 0 1 * *' = Midnight on the 1st of every month + * - '0 2 1,15 * *' = 2 AM on the 1st and 15th of every month + */ +export function setupScraperCron( + cronSchedule: string = '0 2 * * 0', + yearTerm?: string +): CronJob { + info(`[CRON] Setting up room scraper cron: ${cronSchedule}`); + + const job = new CronJob( + cronSchedule, + async () => { + const term = yearTerm || getCurrentYearTerm(); + info(`[CRON] Starting scheduled room scrape for ${term}...`); + + try { + const result = await scrapeRoomData(term, (progress) => { + if (progress.buildings % 5 === 0 && progress.buildings > 0) { + info(`[CRON] Progress: ${progress.buildings} buildings, ${progress.rooms} rooms, ${progress.events} events`); + } + }); + + info(`[CRON] Scheduled scrape complete! Buildings: ${result.buildings}, Rooms: ${result.rooms}, Events: ${result.events}`); + } catch (err) { + error('[CRON] Scheduled scrape failed:'); + error(err); + } + }, + null, // onComplete + true, // start now + 'America/Denver' // timezone (Mountain Time for BYU) + ); + + info('[CRON] Room scraper cron job started'); + return job; +} + +/** + * Example usage - add to your main bot startup file (index.ts or similar): + * + * ```typescript + * import { setupScraperCron } from './roomFinder/cron.js'; + * + * // Run scraper every Sunday at 2 AM Mountain Time + * setupScraperCron(); + * + * // Or with custom schedule (every day at 3 AM): + * setupScraperCron('0 3 * * *'); + * + * // Or with specific year_term: + * setupScraperCron('0 2 * * 0', '20251'); + * ``` + */ diff --git a/src/roomFinder/index.ts b/src/roomFinder/index.ts new file mode 100644 index 00000000..85c78d9c --- /dev/null +++ b/src/roomFinder/index.ts @@ -0,0 +1,16 @@ +// Room Finder Module +// Ported from Python implementation to TypeScript with Prisma + +export * from './types.js'; +export * from './search.js'; +export * from './utils.js'; +export * from './init.js'; + +// Main lookup function for finding available rooms +export { lookup } from './search.js'; + +// Utility functions +export * from './utils.js'; + +// Database initialization functions +export * from './init.js'; diff --git a/src/roomFinder/scraper.ts b/src/roomFinder/scraper.ts new file mode 100644 index 00000000..7f38b58b --- /dev/null +++ b/src/roomFinder/scraper.ts @@ -0,0 +1,509 @@ +/** + * BYU Room Finder Scraper + * + * Scrapes class schedule data from BYU's class schedule system and populates the database. + * Port of the Python scraper from Roomfinder-SQLite. + * + * USAGE: + * - Via Discord: /scraperooms + * - Via code: scrapeRoomData('20251') + * - Via cron: Schedule scrapeRoomData() to run automatically + * + * YEAR_TERM format: YYYYT where T is term (1=Winter, 3=Spring, 4=Summer, 5=Fall) + */ + +import axios from 'axios'; +import * as cheerio from 'cheerio'; +import * as fs from 'fs/promises'; +import * as path from 'path'; +import { db } from '../database/index.js'; +import { error as logError, info as logInfo } from '../logger.js'; + +const COLUMNS = { + course: 0, + class_period: 7, + days: 8, +}; + +// Scraper status tracking +let isScraperRunning = false; +let currentScrapeYearTerm: string | null = null; +let scrapeStartTime: Date | null = null; +const recentLogs: string[] = []; +const MAX_LOG_BUFFER = 10; + +const TIME_FORMAT = /(\d{1,2}):(\d{2})(am|pm)/i; + +// Retry configuration +const MAX_RETRIES = 3; +const INITIAL_RETRY_DELAY = 1000; // 1 second +const REQUEST_TIMEOUT = 30000; // 30 seconds (BYU's server can be slow) + +interface ClassInfo { + name: string; + start: { hours: number; minutes: number; seconds: number }; + end: { hours: number; minutes: number; seconds: number }; + days: string[]; +} + +interface RoomInfo { + description: string; + capacity: number; + classes: ClassInfo[]; +} + +interface ScraperProgress { + buildings: number; + rooms: number; + events: number; + currentBuilding?: string; +} + +/** + * Helper function to add a log to the buffer + */ +function addLog(message: string): void { + const timestamp = new Date().toLocaleTimeString(); + const logEntry = `[${timestamp}] ${message}`; + recentLogs.push(logEntry); + if (recentLogs.length > MAX_LOG_BUFFER) { + recentLogs.shift(); // Remove oldest log + } +} + +/** + * Check if a scrape is currently running + */ +export function isScraperActive(): boolean { + return isScraperRunning; +} + +/** + * Get current scraper status + */ +export function getScraperStatus(): { + isRunning: boolean; + yearTerm: string | null; + startTime: Date | null; + recentLogs: string[]; +} { + return { + isRunning: isScraperRunning, + yearTerm: currentScrapeYearTerm, + startTime: scrapeStartTime, + recentLogs: [...recentLogs], + }; +} + +/** + * Determines the current semester automatically + */ +export function getCurrentYearTerm(): string { + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth() + 1; // 0-indexed + + let term: number; + if (month >= 1 && month <= 4) { + term = 1; // Winter + } else if (month >= 5 && month <= 6) { + term = 3; // Spring + } else if (month >= 7 && month <= 8) { + term = 4; // Summer + } else { + term = 5; // Fall + } + + return `${year}${term}`; +} + +/** + * Sleep for a specified number of milliseconds + */ +async function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * Retry a function with exponential backoff + */ +async function retryWithBackoff( + fn: () => Promise, + context: string, + retries: number = MAX_RETRIES +): Promise { + let lastError: Error | null = null; + + for (let attempt = 0; attempt <= retries; attempt++) { + try { + return await fn(); + } catch (err) { + lastError = err as Error; + + // Check if it's a network error that's worth retrying + const isRetryable = + err && typeof err === 'object' && + ('code' in err && ( + err.code === 'ECONNRESET' || + err.code === 'ETIMEDOUT' || + err.code === 'ECONNREFUSED' || + err.code === 'ENOTFOUND' + )); + + if (!isRetryable || attempt === retries) { + // Not retryable or out of retries + throw err; + } + + // Calculate backoff delay: 1s, 2s, 4s + const delay = INITIAL_RETRY_DELAY * Math.pow(2, attempt); + logInfo(`${context} failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${delay}ms...`); + addLog(`Retry ${attempt + 1}/${retries + 1} for ${context} after ${delay}ms`); + + await sleep(delay); + } + } + + throw lastError; +} + +/** + * Opens cached file or downloads it if not found + */ +async function openOrDownloadFile( + yearTerm: string, + filename: string, + fetchFn: () => Promise +): Promise { + const cacheDir = path.join(process.cwd(), 'scraper', 'out', yearTerm); + const cachePath = path.join(cacheDir, filename); + + try { + const html = await fs.readFile(cachePath, 'utf-8'); + return html; + } catch (err) { + // File doesn't exist, download it with retry logic + logInfo(`Downloading ${filename}...`); + + const html = await retryWithBackoff( + fetchFn, + `Download ${filename}` + ); + + // Create cache directory + await fs.mkdir(cacheDir, { recursive: true }); + + // Save to cache + await fs.writeFile(cachePath, html, 'utf-8'); + + // Sleep to avoid overwhelming the server + await sleep(150); + + return html; + } +} + +/** + * Parses time string like "10:00am" or "2:30pm" to hours/minutes + */ +function parseTime(timeStr: string): { hours: number; minutes: number; seconds: number } { + const match = timeStr.match(TIME_FORMAT); + if (!match) { + logError(`Failed to parse time: ${timeStr}`); + return { hours: 1, minutes: 0, seconds: 0 }; + } + + let hours = parseInt(match[1]!, 10); + const minutes = parseInt(match[2]!, 10); + const period = match[3]!.toLowerCase(); + + // Convert to 24-hour format + if (period === 'pm' && hours !== 12) { + hours += 12; + } else if (period === 'am' && hours === 12) { + hours = 0; + } + + return { hours, minutes, seconds: 0 }; +} + +/** + * Formats time object to "HH:MM:SS" string + */ +function formatTime(time: { hours: number; minutes: number; seconds: number }): string { + const h = String(time.hours).padStart(2, '0'); + const m = String(time.minutes).padStart(2, '0'); + const s = String(time.seconds).padStart(2, '0'); + return `${h}:${m}:${s}`; +} + +/** + * Extracts class information from a table row + */ +function getClassInfo($: cheerio.Root, row: cheerio.Element): ClassInfo { + const cells = $(row).find('td'); + + let start: { hours: number; minutes: number; seconds: number }; + let end: { hours: number; minutes: number; seconds: number }; + + try { + const timePeriod = $(cells[COLUMNS.class_period]) + .text() + .trim() + .replace(/a/gi, 'am') // Replace ALL 'a' with 'am' (global) + .replace(/p/gi, 'pm'); // Replace ALL 'p' with 'pm' (global) + + const [startStr, endStr] = timePeriod.split(' - '); + + if (!startStr || !endStr) { + throw new Error('Invalid time format'); + } + + start = parseTime(startStr); + end = parseTime(endStr); + } catch (err) { + logError(`Error parsing time: ${err}`); + start = { hours: 1, minutes: 0, seconds: 0 }; + end = { hours: 1, minutes: 0, seconds: 0 }; + } + + const daysText = $(cells[COLUMNS.days]).text().trim(); + + // Parse days - handle "Daily" or specific days like "MWF" or "T Th" + let days: string[]; + if (daysText === 'Daily') { + days = ['M', 'T', 'W', 'Th', 'F']; + } else { + // Match: Th, Sa, Su, M, T, W, F (in that order to match Th before T) + const matches = daysText.match(/Th|Sa|Su|M|T|W|F/g); + days = matches || []; + } + + return { + name: $(cells[COLUMNS.course]).text().trim(), + start, + end, + days, + }; +} + +/** + * Fetches and parses room information + */ +async function getRoomInfo(yearTerm: string, building: string, room: string): Promise { + const html = await openOrDownloadFile( + yearTerm, + `${building}-${room}.html`, + async () => { + const response = await axios.post( + 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', + new URLSearchParams({ + year_term: yearTerm, + building: building, + room: room, + }), + { timeout: REQUEST_TIMEOUT } + ); + return response.data; + } + ); + + const $ = cheerio.load(html); + + const description = $('input[name="room_desc"]').val() as string || 'UNKNOWN'; + const capacityStr = $('input[name="capacity"]').val() as string || '0'; + const capacity = parseInt(capacityStr, 10) || 0; + + const classes: ClassInfo[] = []; + + // Find the schedule table (contains "Instructor" header) + const scheduleHeader = $('th:contains("Instructor")'); + if (scheduleHeader.length > 0) { + const table = scheduleHeader.closest('table'); + const rows = table.find('tr').slice(1); // Skip header row + + rows.each((_: number, row: cheerio.Element) => { + classes.push(getClassInfo($, row)); + }); + } + + return { + description, + capacity, + classes, + }; +} + +/** + * Fetches room list for a building + */ +async function* getBuildingsRooms( + yearTerm: string, + buildings: string[] +): AsyncGenerator<[string, string[]]> { + for (const building of buildings) { + const html = await openOrDownloadFile( + yearTerm, + `${building}-list.html`, + async () => { + const response = await axios.post( + 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', + new URLSearchParams({ + e: '@loadRooms', + year_term: yearTerm, + building: building, + }), + { timeout: REQUEST_TIMEOUT } + ); + return response.data; + } + ); + + const $ = cheerio.load(html); + const rooms = $('table a') + .map((_: number, el: cheerio.Element) => $(el).text()) + .get(); + + yield [building, rooms]; + } +} + +/** + * Main scraper function + */ +export async function scrapeRoomData( + yearTerm?: string, + onProgress?: (progress: ScraperProgress) => void +): Promise { + const term = yearTerm || getCurrentYearTerm(); + + // Check if scraper is already running + if (isScraperRunning) { + throw new Error(`Scraper is already running for year_term: ${currentScrapeYearTerm}`); + } + + // Set running status + isScraperRunning = true; + currentScrapeYearTerm = term; + scrapeStartTime = new Date(); + recentLogs.length = 0; // Clear old logs + + logInfo(`Starting scrape for year_term: ${term}`); + addLog(`Starting scrape for ${term}`); + + const progress: ScraperProgress = { + buildings: 0, + rooms: 0, + events: 0, + }; + + try { + // Create cache directory + const cacheDir = path.join(process.cwd(), 'scraper', 'out', term); + await fs.mkdir(cacheDir, { recursive: true }); + + // Clear existing data + logInfo('Clearing existing data...'); + addLog('Clearing existing database data...'); + await db.events.deleteMany({}); + await db.rooms.deleteMany({}); + await db.buildings.deleteMany({}); + + // Fetch building list from main page + logInfo('Fetching building list...'); + addLog('Fetching building list from BYU...'); + const indexHtml = await openOrDownloadFile( + term, + 'classRoom2.cgi', + async () => { + const response = await axios.post( + 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', + new URLSearchParams({ year_term: term }), + { timeout: REQUEST_TIMEOUT } + ); + return response.data; + } + ); + + const $ = cheerio.load(indexHtml); + const buildings = $('select[name="Building"] option') + .map((_: number, el: cheerio.Element) => $(el).val() as string) + .get() + .filter((val: string) => val && val.trim()); // Remove empty values + + logInfo(`Found ${buildings.length} buildings`); + addLog(`Found ${buildings.length} buildings to process`); + + // Iterate through buildings and rooms + for await (const [building, rooms] of getBuildingsRooms(term, buildings)) { + progress.currentBuilding = building; + logInfo(`Processing building: ${building} (${rooms.length} rooms)`); + addLog(`Processing ${building} (${rooms.length} rooms)`); + + // Insert building + const buildingRecord = await db.buildings.create({ + data: { name: building }, + }); + progress.buildings++; + + for (const room of rooms) { + const roomInfo = await getRoomInfo(term, building, room); + + // Insert room + const roomRecord = await db.rooms.create({ + data: { + buildingId: buildingRecord.id, + number: room, + description: roomInfo.description, + }, + }); + progress.rooms++; + + // Insert events/classes + for (const classInfo of roomInfo.classes) { + await db.events.create({ + data: { + roomId: roomRecord.id, + name: classInfo.name, + days: JSON.stringify(classInfo.days), + startTime: formatTime(classInfo.start), + endTime: formatTime(classInfo.end), + }, + }); + progress.events++; + + if (progress.events % 100 === 0) { + logInfo(` Processed ${progress.events} events...`); + } + } + } + + if (onProgress) { + onProgress(progress); + } + } + + logInfo(`Scrape complete! Buildings: ${progress.buildings}, Rooms: ${progress.rooms}, Events: ${progress.events}`); + addLog(`✅ Complete! ${progress.buildings} buildings, ${progress.rooms} rooms, ${progress.events} events`); + + return progress; + + } catch (err) { + logError('Scraper error:'); + logError(err); + addLog(`❌ Error: ${err instanceof Error ? err.message : 'Unknown error'}`); + throw err; + } finally { + // Always clear running status when done + isScraperRunning = false; + currentScrapeYearTerm = null; + scrapeStartTime = null; + } +} + +/** + * Validates year_term format (YYYYT where T is 1, 3, 4, or 5) + */ +export function isValidYearTerm(yearTerm: string): boolean { + return /^\d{4}[1345]$/.test(yearTerm); +} diff --git a/src/roomFinder/search.ts b/src/roomFinder/search.ts new file mode 100644 index 00000000..09e74afe --- /dev/null +++ b/src/roomFinder/search.ts @@ -0,0 +1,262 @@ +import { db } from '../database/index.js'; +import { type RoomSearchParams, type RoomAvailabilityResult, type EventInfo, DAY_MAP } from './types.js'; +import { Prisma } from '@prisma/client'; + +export async function lookup(params: RoomSearchParams): Promise { + const { building, room, timeType, timeA, timeB, days } = params; + + // Fetch building record if building is specified + let buildingRecord = null; + if (building && building !== 'ANY') { + buildingRecord = await db.buildings.findFirst({ + where: { name: building } + }); + + if (!buildingRecord) { + return []; + } + } + + // Start with all classroom rooms + let query = db.rooms.findMany({ + where: { + description: 'CLASSROOM' + }, + include: { + building: true + } + }); + + // Filter by building if specified + if (building && building !== 'ANY' && buildingRecord) { + query = db.rooms.findMany({ + where: { + description: 'CLASSROOM', + buildingId: buildingRecord.id + }, + include: { + building: true + } + }); + } + + // Get current date/time information in US/Mountain timezone + // Python uses: datetime.now(timezone('US/Mountain')) + const now = new Date(); + const mountainTime = new Date(now.toLocaleString('en-US', { timeZone: 'America/Denver' })); + const currentTime = mountainTime.toTimeString().slice(0, 8); // HH:MM:SS format + const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + const currentDayAbbrev = dayNames[mountainTime.getDay()]; + const currentDay = DAY_MAP[currentDayAbbrev || 'Mon'] || 'M'; + + let conflictingEventsQuery = db.events.findMany({ + include: { + room: true + } + }); + + let currentEventsQuery = db.events.findMany({ + include: { + room: { + include: { + building: true + } + } + } + }); + + if (timeType === 'now') { + // Check for conflicting events right now + // Python: .where(SQL("EXISTS (SELECT 1 FROM json_each(days) WHERE value = ?)", (day,))) + conflictingEventsQuery = db.events.findMany({ + where: { + AND: [ + { + days: { + contains: `"${currentDay}"` + } + }, + { + startTime: { lte: currentTime } + }, + { + endTime: { gt: currentTime } + } + ] + }, + include: { + room: true + } + }); + } else if (timeType === 'when') { + // Get current events for specific room + if (!building || !room) { + throw new Error('Building and room are required for "when" queries'); + } + + currentEventsQuery = db.events.findMany({ + where: { + AND: [ + { + room: { + building: { + name: building + }, + number: room + } + }, + { + endTime: { + gte: currentTime + } + }, + { + days: { + contains: `"${currentDay}"` + } + } + ] + }, + include: { + room: { + include: { + building: true + } + } + }, + orderBy: { + startTime: 'asc' + }, + take: 5 + }); + } else if (timeType === 'at') { + if (!timeA || !isValidTimeFormat(timeA)) { + throw new Error('Valid timeA is required for "at" queries'); + } + + // Check for conflicting events at specific time + // Python uses: .where(SQL("EXISTS (SELECT 1 FROM json_each(days) WHERE value IN (...))", days)) + // For SQLite JSON arrays, we need to check if any of the input days are in the JSON array + if (days.length === 0) { + // No days specified, return empty + conflictingEventsQuery = db.events.findMany({ + where: { id: -1 }, // No results + include: { room: true } + }); + } else { + conflictingEventsQuery = db.events.findMany({ + where: { + AND: [ + { + OR: days.map(day => ({ + days: { contains: `"${day}"` } + })) + }, + { + startTime: { lte: timeA } + }, + { + endTime: { gt: timeA } + } + ] + }, + include: { + room: true + } + }); + } + } else if (timeType === 'between') { + if (!timeA || !timeB || !isValidTimeFormat(timeA) || !isValidTimeFormat(timeB)) { + throw new Error('Valid timeA and timeB are required for "between" queries'); + } + + // Check for conflicting events in time range + // Python uses: .where(SQL("EXISTS (SELECT 1 FROM json_each(days) WHERE value IN (...))", days)) + // For SQLite JSON arrays, we need to check if any of the input days are in the JSON array + if (days.length === 0) { + // No days specified, return empty + conflictingEventsQuery = db.events.findMany({ + where: { id: -1 }, // No results + include: { room: true } + }); + } else { + conflictingEventsQuery = db.events.findMany({ + where: { + AND: [ + { + OR: days.map(day => ({ + days: { contains: `"${day}"` } + })) + }, + { + OR: [ + { + AND: [ + { startTime: { lte: timeA } }, + { endTime: { gt: timeA } } + ] + }, + { + AND: [ + { startTime: { lt: timeB } }, + { endTime: { gte: timeB } } + ] + }, + { + AND: [ + { startTime: { gte: timeA } }, + { endTime: { lte: timeB } } + ] + } + ] + } + ] + }, + include: { + room: true + } + }); + } + } + + if (timeType === 'when') { + // Return current events for the room + const currentEvents = await currentEventsQuery; + return currentEvents.map((event: { name: any; startTime: any; endTime: any; }) => ({ + name: event.name, + startTime: event.startTime, + endTime: event.endTime + })); + } else { + // Get conflicting room IDs + const conflictingEvents = await conflictingEventsQuery; + const conflictingRoomIds = conflictingEvents.map((event: { roomId: any; }) => event.roomId); + + // Get available rooms (not in conflicting events) + const availableRooms = await db.rooms.findMany({ + where: { + id: { + notIn: conflictingRoomIds.length > 0 ? conflictingRoomIds : [-1] + }, + description: 'CLASSROOM', + ...(building && building !== 'ANY' && buildingRecord ? { buildingId: buildingRecord.id } : {}) + }, + include: { + building: true + }, + orderBy: [ + { building: { name: 'asc' } }, + { number: 'asc' } + ] + }); + + return availableRooms.map((room: { number: any; building: { name: any; }; }) => ({ + roomNumber: room.number, + buildingName: room.building.name + })); + } +} + +function isValidTimeFormat(time: string): boolean { + return /^[0-2][0-9]:[0-5][0-9]:[0-5][0-9]$/.test(time); +} diff --git a/src/roomFinder/types.ts b/src/roomFinder/types.ts new file mode 100644 index 00000000..45fafdba --- /dev/null +++ b/src/roomFinder/types.ts @@ -0,0 +1,62 @@ +export interface Building { + id: number; + name: string; +} + +export interface Room { + id: number; + buildingId: number; + building: Building; + number: string; + description: string; +} + +export interface Event { + id: number; + roomId: number; + room: Room; + name: string; + days: string; // JSON array of days: ["M", "T", "W", "Th", "F", "Sa", "Su"] + startTime: string; // TIME format: "HH:MM:SS" + endTime: string; // TIME format: "HH:MM:SS" +} + +export interface RoomSearchParams { + building?: string; + room?: string; + timeType: 'now' | 'when' | 'at' | 'between'; + timeA?: string; // For 'at' and 'between' types + timeB?: string; // For 'between' type + days: string[]; // Array of day abbreviations +} + +export interface RoomAvailabilityResult { + roomNumber: string; + buildingName: string; +} + +export interface EventInfo { + name: string; + startTime: string; + endTime: string; +} + +export const DAY_MAP: Record = { + 'Mon': 'M', + 'Tue': 'T', + 'Wed': 'W', + 'Thu': 'Th', + 'Fri': 'F', + 'Sat': 'Sa', + 'Sun': 'Su', +}; + +export const REVERSE_DAY_MAP: Record = { + 'M': 'Mon', + 'T': 'Tue', + 'W': 'Wed', + 'Th': 'Thu', + 'F': 'Fri', + 'Sa': 'Sat', + 'Su': 'Sun', +}; diff --git a/src/roomFinder/utils.ts b/src/roomFinder/utils.ts new file mode 100644 index 00000000..dbbe9a3d --- /dev/null +++ b/src/roomFinder/utils.ts @@ -0,0 +1,150 @@ +import { db } from '../database/index.js'; +import type { Building, Room, Event, RoomSearchParams, RoomAvailabilityResult, EventInfo } from './types.js'; + +/** + * Get all available buildings + */ +export async function getAllBuildings(): Promise { + return await db.buildings.findMany({ + orderBy: { + name: 'asc' + } + }); +} + +/** + * Get all rooms for a specific building + */ +export async function getRoomsByBuilding(buildingName: string): Promise { + return await db.rooms.findMany({ + where: { + building: { + name: buildingName + } + }, + include: { + building: true + }, + orderBy: { + number: 'asc' + } + }); +} + +/** + * Get all rooms + */ +export async function getAllRooms(): Promise { + return await db.rooms.findMany({ + include: { + building: true + }, + orderBy: [ + { building: { name: 'asc' } }, + { number: 'asc' } + ] + }); +} + +/** + * Get events for a specific room on a specific day + */ +export async function getRoomEvents(roomId: number, day: string): Promise[]> { + return await db.events.findMany({ + where: { + roomId: roomId, + days: { + contains: `"${day}"` + } + }, + orderBy: { + startTime: 'asc' + } + }); +} + +/** + * Check if a room is available at a specific time + */ +export async function isRoomAvailable(roomId: number, time: string, day: string): Promise { + const events = await getRoomEvents(roomId, day); + + for (const event of events) { + if (event.startTime <= time && event.endTime > time) { + return false; // Room is occupied + } + } + + return true; // Room is available +} + +/** + * Get available rooms at a specific time + */ +export async function getAvailableRooms(time: string, day: string, buildingName?: string): Promise { + const allRooms = await getAllRooms(); + const availableRooms: RoomAvailabilityResult[] = []; + + for (const room of allRooms) { + // Skip if building filter is specified and doesn't match + if (buildingName && room.building.name !== buildingName) { + continue; + } + + const isAvailable = await isRoomAvailable(room.id, time, day); + if (isAvailable) { + availableRooms.push({ + roomNumber: room.number, + buildingName: room.building.name + }); + } + } + + return availableRooms.sort((a, b) => { + if (a.buildingName !== b.buildingName) { + return a.buildingName.localeCompare(b.buildingName); + } + return a.roomNumber.localeCompare(b.roomNumber); + }); +} + +/** + * Parse days array from JSON string + */ +export function parseDays(daysJson: string): string[] { + try { + const parsed = JSON.parse(daysJson); + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } +} + +/** + * Format days array to JSON string + */ +export function formatDays(days: string[]): string { + return JSON.stringify(days); +} + +/** + * Validate time format (HH:MM:SS) + */ +export function isValidTimeFormat(time: string): boolean { + return /^[0-2][0-9]:[0-5][0-9]:[0-5][0-9]$/.test(time); +} + +/** + * Get current time in HH:MM:SS format + */ +export function getCurrentTime(): string { + return new Date().toTimeString().slice(0, 8); +} + +/** + * Get current day abbreviation + */ +export function getCurrentDay(): string { + const days = ['Su', 'M', 'T', 'W', 'Th', 'F', 'Sa']; + return days[new Date().getDay()] || ''; +} From dad716670340d4164375c06c888cdf834cacd2eb Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Wed, 22 Oct 2025 20:09:42 -0600 Subject: [PATCH 03/14] refactor: migrate room finder to use new API module and add day filtering --- src/commands/findRoom.test.ts | 39 ----- src/commands/findRoom.ts | 319 +++++++++++++++++----------------- src/commands/index.ts | 2 + src/commands/scrapeRooms.ts | 113 ++++++++++++ 4 files changed, 275 insertions(+), 198 deletions(-) delete mode 100644 src/commands/findRoom.test.ts create mode 100644 src/commands/scrapeRooms.ts diff --git a/src/commands/findRoom.test.ts b/src/commands/findRoom.test.ts deleted file mode 100644 index a8ce89dc..00000000 --- a/src/commands/findRoom.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, test, vi } from 'vitest'; - -vi.mock('../constants/meta.js', async () => { - const { repo } = - await vi.importActual('../constants/meta.js'); - return { - // Version changes frequently, so use a consistent version number to test with: - appVersion: 'X.X.X', - repo, - }; -}); - -import { convertTo12Hour } from './findRoom.js'; - -describe('findRoom', () => { - test('convertTo12Hour 8AM', () => { - const time = '08:00:00'; - const result = convertTo12Hour(time); - expect(result).toEqual('8:00 AM'); - }); - - test('convertTo12Hour 8PM', () => { - const time = '20:00:00'; - const result = convertTo12Hour(time); - expect(result).toEqual('8:00 PM'); - }); - - test('convertTo12Hour Truncate', () => { - const time = '12:34:56'; - const result = convertTo12Hour(time); - expect(result).toEqual('12:34 PM'); - }); - - test('convertTo12Hour Err', () => { - const time = '12:34'; - const result = convertTo12Hour(time); - expect(result).toEqual('ERR'); - }); -}); diff --git a/src/commands/findRoom.ts b/src/commands/findRoom.ts index 14dd879d..9365c070 100644 --- a/src/commands/findRoom.ts +++ b/src/commands/findRoom.ts @@ -1,24 +1,7 @@ -import { array, boolean, string, tuple, type as schema } from 'superstruct'; import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; -import { URL } from 'node:url'; - -import { fetchJson } from '../helpers/fetch.js'; +import { searchNow, searchAt, searchBetween, searchWhen, type RoomsResponse, type WhenResponse } from '../roomFinder/api.js'; import { error } from '../logger.js'; -const getRoomInfoResponse = schema({ - busySince: string(), - busyUntil: string(), - isInUse: boolean(), -}); - -type GetRoomInfoResponse = typeof getRoomInfoResponse.TYPE; - -const getRoomsResponse = schema({ - Rooms: array(tuple([string(), string()])), -}); - -type GetRoomsResponse = typeof getRoomsResponse.TYPE; - const timeChoices = [ { name: '8:00 AM', value: '08:00:00' }, { name: '8:30 AM', value: '08:30:00' }, @@ -74,6 +57,16 @@ const bldgChoices = [ { name: 'WVB', value: 'WVB' }, ] as const; +const dayChoices = [ + { name: 'Monday', value: 'Mon' }, + { name: 'Tuesday', value: 'Tue' }, + { name: 'Wednesday', value: 'Wed' }, + { name: 'Thursday', value: 'Thu' }, + { name: 'Friday', value: 'Fri' }, + { name: 'Saturday', value: 'Sat' }, + { name: 'Sunday', value: 'Sun' }, +] as const; + export function convertTo12Hour(time: string): string { const [hours, minutes, seconds] = time.split(':').map(Number); if (hours !== undefined && minutes !== undefined && seconds !== undefined) { @@ -84,57 +77,9 @@ export function convertTo12Hour(time: string): string { return 'ERR'; } -async function _getRoomsFromEndpoint(endpoint: URL): Promise { - try { - return await fetchJson(endpoint, getRoomsResponse); - } catch (error_) { - error('Error in getting Room Info:'); - error(error_); - const err: GetRoomsResponse = { - Rooms: [], - }; - return err; - } -} - -async function _getRoomsNow(building: string): Promise { - const url = new URL(`https://pi.zyancey.com/now/${building}`); - return await _getRoomsFromEndpoint(url); -} - -async function _getRoomsAt(building: string, timeA: string): Promise { - const url = new URL(`https://pi.zyancey.com/at/${building}/${timeA}`); - return await _getRoomsFromEndpoint(url); -} - -async function _getRoomsBetween( - building: string, - timeA: string, - timeB: string -): Promise { - const url = new URL(`https://pi.zyancey.com/between/${building}/${timeA}/${timeB}`); - return await _getRoomsFromEndpoint(url); -} - -async function _getWhenRoom(building: string, room: string): Promise { - try { - const endpoint = new URL(`https://pi.zyancey.com/when/${building}/${room}`); - return await fetchJson(endpoint, getRoomInfoResponse); - } catch (error_) { - error('Error in getting Room Info:'); - error(error_); - const err: GetRoomInfoResponse = { - busySince: 'Error', - busyUntil: 'Error', - isInUse: false, - }; - return err; - } -} - const builder = new SlashCommandBuilder() .setName('findroom') - .setDescription('Finds you a room to study in on campus!') + .setDescription('Finds rooms available at a given time') .addSubcommand(subcommand => subcommand .setName('now') @@ -142,7 +87,7 @@ const builder = new SlashCommandBuilder() .addStringOption(option => option .setName('building') - .setDescription('The building acronym to search in, type any to see all options') + .setDescription('The building acronym to search in') .addChoices(...bldgChoices) .setRequired(true) ) @@ -161,36 +106,64 @@ const builder = new SlashCommandBuilder() .addStringOption(option => option .setName('building') - .setDescription('The building acronym to search in, type any to see all options') + .setDescription('The building acronym to search in') .addChoices(...bldgChoices) .setRequired(true) ) + .addStringOption(option => + option + .setName('day1') + .setDescription('First day to search (optional)') + .addChoices(...dayChoices) + .setRequired(false) + ) + .addStringOption(option => + option + .setName('day2') + .setDescription('Second day to search (optional)') + .addChoices(...dayChoices) + .setRequired(false) + ) ) .addSubcommand(subcommand => subcommand .setName('between') - .setDescription('Finds you an available room on campus at a given time!') + .setDescription('Finds you an available room on campus during a time range!') .addStringOption(option => option .setName('start_time') - .setDescription('What time would you like to search for?') + .setDescription('Start time') .addChoices(...timeChoices) .setRequired(true) ) .addStringOption(option => option .setName('end_time') - .setDescription('What time would you like to search for?') + .setDescription('End time') .addChoices(...timeChoices) .setRequired(true) ) .addStringOption(option => option .setName('building') - .setDescription('The building acronym to search in, type any to see all options') + .setDescription('The building acronym to search in') .addChoices(...bldgChoices) .setRequired(true) ) + .addStringOption(option => + option + .setName('day1') + .setDescription('First day to search (optional)') + .addChoices(...dayChoices) + .setRequired(false) + ) + .addStringOption(option => + option + .setName('day2') + .setDescription('Second day to search (optional)') + .addChoices(...dayChoices) + .setRequired(false) + ) ) .addSubcommand(subcommand => subcommand @@ -199,7 +172,7 @@ const builder = new SlashCommandBuilder() .addStringOption(option => option .setName('building') - .setDescription('The building acronym to search in, type any to see all options') + .setDescription('The building acronym to search in') .addChoices(...bldgChoices) .setRequired(true) ) @@ -214,126 +187,154 @@ const builder = new SlashCommandBuilder() export const findRoom: GlobalCommand = { info: builder, requiresGuild: false, - async execute({ replyPrivately, options }): Promise { + async execute({ reply, options }): Promise { const input_bldg = options.getString('building'); const input_room = options.getString('room'); const input_timeA = options.getString('start_time'); const input_timeB = options.getString('end_time'); + const day1 = options.getString('day1'); + const day2 = options.getString('day2'); const type = options.getSubcommand(); let embedTitle = ''; let embedDescription = ''; - let embedThumbnail = 'https://pi.zyancey.com/img/room-finder-logo.png'; // not currently used, placeholder so that it doesn't show + let embedThumbnail = 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Brigham_Young_University_medallion.svg/240px-Brigham_Young_University_medallion.svg.png'; + let embedColor = 0x0066cc; - switch (type) { - case 'now': { - if (input_bldg !== null) { - const requestedList = await _getRoomsNow(input_bldg); - if (requestedList.Rooms.length === 0) { - embedTitle = `No rooms available now in the ${input_bldg}`; - embedDescription = 'Try again later!'; - } else { - const roomString = requestedList.Rooms.map(room => room.reverse().join(', ')).join( - '\n' - ); - embedTitle = `Rooms available now in the ${input_bldg}`; - embedDescription = roomString; + try { + switch (type) { + case 'now': { + if (input_bldg !== null) { + const requestedList = await searchNow(input_bldg); + const locationText = input_bldg === 'ANY' ? 'anywhere on campus' : `in the ${input_bldg}`; + if (requestedList.Rooms.length === 0) { + embedTitle = `No rooms available now ${locationText}`; + embedDescription = 'Try again later!'; + embedColor = 0xff0000; // Red + } else { + const roomString = requestedList.Rooms.map(room => + `${room[1]}, ${room[0]}` + ).join('\n'); + embedTitle = `Rooms available now ${locationText}`; + embedDescription = roomString; + } } + break; } - break; - } - case 'at': { - if (input_bldg !== null && input_timeA !== null) { - const requestedList = await _getRoomsAt(input_bldg, input_timeA); - if (requestedList.Rooms.length === 0) { - embedTitle = `No rooms available at ${convertTo12Hour( - input_timeA - )} in the ${input_bldg}`; - embedDescription = 'Try again later!'; - } else { - const roomString = requestedList.Rooms.map(room => room.reverse().join(', ')).join( - '\n' - ); - embedTitle = `Rooms available in the ${input_bldg} at ${convertTo12Hour(input_timeA)}`; - embedDescription = roomString; + case 'at': { + if (input_bldg !== null && input_timeA !== null) { + const days = [day1, day2].filter(d => d !== null) as string[]; + const requestedList = await searchAt(input_bldg, input_timeA, days); + const locationText = input_bldg === 'ANY' ? 'anywhere on campus' : `in the ${input_bldg}`; + if (requestedList.Rooms.length === 0) { + embedTitle = `No rooms available at ${convertTo12Hour( + input_timeA + )} ${locationText}`; + embedDescription = days.length > 0 + ? `Days: ${days.join(', ')}\nTry again later!` + : 'Try again later!'; + embedColor = 0xff0000; + } else { + const roomString = requestedList.Rooms.map(room => + `${room[1]}, ${room[0]}` + ).join('\n'); + embedTitle = `Rooms available ${locationText} at ${convertTo12Hour(input_timeA)}`; + embedDescription = days.length > 0 + ? `Days: ${days.join(', ')}\n\n${roomString}` + : roomString; + } } + break; } - break; - } - case 'between': { - if (input_bldg !== null && input_timeA !== null && input_timeB !== null) { - const requestedList = await _getRoomsBetween(input_bldg, input_timeA, input_timeB); - if (requestedList.Rooms.length === 0) { - embedTitle = `No rooms available between ${convertTo12Hour( - input_timeA - )} and ${convertTo12Hour(input_timeB)}`; - embedDescription = 'Try again later!'; - } else { - const roomString = requestedList.Rooms.map(room => room.reverse().join(', ')).join( - '\n' - ); - embedTitle = `Rooms available in the ${input_bldg} between ${convertTo12Hour( - input_timeA - )} and ${convertTo12Hour(input_timeB)}`; - embedDescription = roomString; + case 'between': { + if (input_bldg !== null && input_timeA !== null && input_timeB !== null) { + const days = [day1, day2].filter(d => d !== null) as string[]; + const requestedList = await searchBetween(input_bldg, input_timeA, input_timeB, days); + const locationText = input_bldg === 'ANY' ? 'anywhere on campus' : `in the ${input_bldg}`; + if (requestedList.Rooms.length === 0) { + embedTitle = `No rooms available between ${convertTo12Hour( + input_timeA + )} and ${convertTo12Hour(input_timeB)}`; + embedDescription = days.length > 0 + ? `Days: ${days.join(', ')}\nTry again later!` + : 'Try again later!'; + embedColor = 0xff0000; + } else { + const roomString = requestedList.Rooms.map(room => + `${room[1]}, ${room[0]}` + ).join('\n'); + embedTitle = `Rooms available ${locationText} between ${convertTo12Hour( + input_timeA + )} and ${convertTo12Hour(input_timeB)}`; + embedDescription = days.length > 0 + ? `Days: ${days.join(', ')}\n\n${roomString}` + : roomString; + } } + break; } - break; - } - case 'when': { - if (input_bldg !== null && input_room !== null) { - const requestedList = await _getWhenRoom(input_bldg, input_room); - const busySince = - requestedList.busySince === '' - ? requestedList.busySince - : requestedList.busySince.slice(11, 19); - const busyUntil = - requestedList.busyUntil === '' - ? requestedList.busyUntil - : requestedList.busyUntil.slice(11, 19); - // FORMAT 2023-02-06T12:15:00-07:00 - const isInUse = requestedList.isInUse; + case 'when': { + if (input_bldg !== null && input_room !== null) { + const requestedList = await searchWhen(input_bldg, input_room); + const busySince = + requestedList.busySince === '' + ? requestedList.busySince + : requestedList.busySince.slice(11, 19); + const busyUntil = + requestedList.busyUntil === '' + ? requestedList.busyUntil + : requestedList.busyUntil.slice(11, 19); + const isInUse = requestedList.isInUse; - let roomString = ''; + let roomString = ''; - if (busySince === '' || busyUntil === '') { - roomString = `I couldn't find any information today for room ${input_room} in the ${input_bldg}, either it doesn't exist or it has no scheduled events for the remainder of the day.`; - } else if (isInUse) { - roomString = `This room is currently busy from ${convertTo12Hour( - busySince - )} to ${convertTo12Hour(busyUntil)}`; - } else { - roomString = `Room ${input_room} is currently free, with its next event scheduled from ${convertTo12Hour( - busySince - )} to ${convertTo12Hour(busyUntil)}`; - } + if (busySince === '' || busyUntil === '') { + roomString = `I couldn't find any information today for room ${input_room} in the ${input_bldg}, either it doesn't exist or it has no scheduled events for the remainder of the day.`; + } else if (isInUse) { + roomString = `This room is currently busy from ${convertTo12Hour( + busySince + )} to ${convertTo12Hour(busyUntil)}`; + embedColor = 0xff0000; + } else { + roomString = `Room ${input_room} is currently free, with its next event scheduled from ${convertTo12Hour( + busySince + )} to ${convertTo12Hour(busyUntil)}`; + embedColor = 0x00ff00; // Green + } - embedTitle = `Room ${input_room} in the ${input_bldg}`; - embedThumbnail = `https://aim-classroom-img-prd.byu-oit-sis-prd.amazon.byu.edu/?id=${input_bldg}/${input_room}f.jpg`; - embedDescription = roomString; + embedTitle = `Room ${input_room} in the ${input_bldg}`; + embedThumbnail = `https://aim-classroom-img-prd.byu-oit-sis-prd.amazon.byu.edu/?id=${input_bldg}/${input_room}f.jpg`; + embedDescription = roomString; + } + break; } - break; } + } catch (error_) { + error('Error in room finder:'); + error(error_); + embedTitle = 'Error'; + embedDescription = `An error occurred: ${error_ instanceof Error ? error_.message : 'Unknown error'}`; + embedColor = 0xff0000; } - // we should have the data in response, build the embed. + // Build the embed const embed = new EmbedBuilder() .setTitle(embedTitle) .setThumbnail(embedThumbnail) .setDescription(embedDescription) + .setColor(embedColor) .setTimestamp() .setFooter({ - text: 'BYU Room Finder', - // Going to locally host this on my server soon, I'll make that change when I come back and add thumbnails for the building searches + text: 'BYU Room Finder (Native)', iconURL: 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Brigham_Young_University_medallion.svg/240px-Brigham_Young_University_medallion.svg.png', }); - // send the embed back to the client. - await replyPrivately({ + // Send the embed back to the client + await reply({ embeds: [embed], }); }, diff --git a/src/commands/index.ts b/src/commands/index.ts index 4ac92508..9752f087 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -34,6 +34,7 @@ import { findRoom } from './findRoom.js'; import { help } from './help.js'; import { isCasDown } from './isCasDown.js'; import { profile } from './profile.js'; +import { scrapeRooms } from './scrapeRooms.js'; import { sendtag } from './sendtag.js'; import { setReactboard } from './setReactboard.js'; import { stats } from './stats.js'; @@ -50,6 +51,7 @@ _add(findRoom); _add(help); _add(isCasDown); _add(profile); +_add(scrapeRooms); _add(sendtag); _add(setReactboard); _add(stats); diff --git a/src/commands/scrapeRooms.ts b/src/commands/scrapeRooms.ts new file mode 100644 index 00000000..ce0bc652 --- /dev/null +++ b/src/commands/scrapeRooms.ts @@ -0,0 +1,113 @@ +import { EmbedBuilder, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; +import { scrapeRoomData, getCurrentYearTerm, isValidYearTerm, getScraperStatus } from '../roomFinder/scraper.js'; +import { error, info } from '../logger.js'; + +const builder = new SlashCommandBuilder() + .setName('scraperooms') + .setDescription('[ADMIN] Scrape BYU class schedule data into the room finder database') + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) + .addStringOption(option => + option + .setName('year_term') + .setDescription('Semester to scrape (YYYYT format: 1=Winter , 3=Spring , 4=Summer , 5=Fall)') + .setRequired(false) + ); + +export const scrapeRooms: GlobalCommand = { + info: builder, + requiresGuild: false, + async execute({ replyPrivately, options }): Promise { + const inputYearTerm = options.getString('year_term'); + const yearTerm = inputYearTerm || getCurrentYearTerm(); + + // Check if scraper is already running + const status = getScraperStatus(); + if (status.isRunning) { + const elapsed = status.startTime + ? Math.floor((Date.now() - status.startTime.getTime()) / 1000 / 60) + : 0; + + const logsText = status.recentLogs.length > 0 + ? '```\n' + status.recentLogs.join('\n') + '\n```' + : 'No recent logs available.'; + + await replyPrivately({ + embeds: [ + new EmbedBuilder() + .setTitle('⚠️ Scraper Already Running') + .setDescription( + `A scrape is currently in progress!\n\n` + + `**Year/Term**: ${status.yearTerm}\n` + + `**Running for**: ${elapsed} minutes\n\n` + + `**Recent Activity**:\n${logsText}\n\n` + + `Please wait for the current scrape to complete before starting another.` + ) + .setColor(0xffa500) // Orange + .setTimestamp() + .setFooter({ + text: 'Check console for full logs', + }), + ], + }); + return; + } + + // Validate year_term format + if (!isValidYearTerm(yearTerm)) { + await replyPrivately({ + embeds: [ + new EmbedBuilder() + .setTitle('❌ Invalid Year/Term') + .setDescription( + `Invalid format: \`${yearTerm}\`\n\n` + + `**Format**: YYYYT where T is:\n` + + `• 1 = Winter\n` + + `• 3 = Spring\n` + + `• 4 = Summer\n` + + `• 5 = Fall\n\n` + + `**Examples**:\n` + + `• \`20251\` = Winter 2025\n` + + `• \`20253\` = Spring 2025\n` + + `• \`20255\` = Fall 2025` + ) + .setColor(0xff0000) + .setTimestamp(), + ], + }); + return; + } + + // Reply immediately - don't wait for scraper + await replyPrivately({ + embeds: [ + new EmbedBuilder() + .setTitle('🔄 Room Scraper Started') + .setDescription( + `**Year/Term**: ${yearTerm}\n\n` + + `The scraper is now running in the background.\n` + + `This will take **10-15 minutes** on first run.\n\n` + + `Check the logs or run \`/scraperooms\` again to check progress.` + ) + .setColor(0xffa500) // Orange + .setTimestamp() + .setFooter({ + text: 'Check console logs for progress', + }), + ], + }); + + // Start scraper in background (don't await) + info(`[SCRAPER] Starting scrape for ${yearTerm}...`); + scrapeRoomData(yearTerm, (progress) => { + if (progress.buildings % 5 === 0 && progress.buildings > 0) { + info(`[SCRAPER] Progress: Buildings=${progress.buildings}, Rooms=${progress.rooms}, Events=${progress.events}, Current=${progress.currentBuilding}`); + } + }).then((result) => { + info(`[SCRAPER] ✅ Complete! Buildings: ${result.buildings}, Rooms: ${result.rooms}, Events: ${result.events}`); + }).catch((err: unknown) => { + error('[SCRAPER] ❌ Error during scrape:'); + error(err); + // Error already logged to buffer by scraper + }); + }, +}; From a5369292fa4b63acfe6b1f21ca9c375a04acc0cf Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Wed, 22 Oct 2025 20:20:53 -0600 Subject: [PATCH 04/14] test: add unit tests for findRoom, scrapeRooms, and scraper utilities --- src/commands/findRoom.test.ts | 284 ++++++++++++++++++++++++++ src/commands/scrapeRooms.test.ts | 184 +++++++++++++++++ src/roomFinder/scraper.test.ts | 123 +++++++++++ src/roomFinder/search.test.ts | 323 +++++++++++++++++++++++++++++ src/roomFinder/utils.test.ts | 336 +++++++++++++++++++++++++++++++ 5 files changed, 1250 insertions(+) create mode 100644 src/commands/findRoom.test.ts create mode 100644 src/commands/scrapeRooms.test.ts create mode 100644 src/roomFinder/scraper.test.ts create mode 100644 src/roomFinder/search.test.ts create mode 100644 src/roomFinder/utils.test.ts diff --git a/src/commands/findRoom.test.ts b/src/commands/findRoom.test.ts new file mode 100644 index 00000000..10e9f18d --- /dev/null +++ b/src/commands/findRoom.test.ts @@ -0,0 +1,284 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import type { EmbedBuilder } from 'discord.js'; + +import { findRoom, convertTo12Hour } from './findRoom.js'; + +// Mock the API module +vi.mock('../roomFinder/api.js', () => ({ + searchNow: vi.fn(() => Promise.resolve({ Rooms: [] })), + searchAt: vi.fn(() => Promise.resolve({ Rooms: [] })), + searchBetween: vi.fn(() => Promise.resolve({ Rooms: [] })), + searchWhen: vi.fn(() => Promise.resolve({ + isInUse: false, + busySince: '', + busyUntil: '', + })), +})); + +// Mock the logger +vi.mock('../logger.js', () => ({ + error: vi.fn(), +})); + +import { searchNow, searchAt, searchBetween, searchWhen } from '../roomFinder/api.js'; + +describe('findRoom command', () => { + const mockReply = vi.fn(); + const mockGetString = vi.fn(); + const mockGetSubcommand = vi.fn(); + let context: TextInputCommandContext; + + beforeEach(() => { + context = { + reply: mockReply, + options: { + getString: mockGetString, + getSubcommand: mockGetSubcommand, + }, + } as unknown as TextInputCommandContext; + + vi.clearAllMocks(); + }); + + describe('convertTo12Hour', () => { + test('converts morning time correctly', () => { + expect(convertTo12Hour('09:30:00')).toBe('9:30 AM'); + }); + + test('converts afternoon time correctly', () => { + expect(convertTo12Hour('14:30:00')).toBe('2:30 PM'); + }); + + test('converts midnight correctly', () => { + expect(convertTo12Hour('00:00:00')).toBe('12:00 AM'); + }); + + test('converts noon correctly', () => { + expect(convertTo12Hour('12:00:00')).toBe('12:00 PM'); + }); + + test('returns ERR for invalid time', () => { + expect(convertTo12Hour('invalid')).toBe('ERR'); + }); + }); + + describe('now subcommand', () => { + beforeEach(() => { + mockGetSubcommand.mockReturnValue('now'); + mockGetString.mockImplementation((name) => { + if (name === 'building') return 'TMCB'; + return null; + }); + }); + + test('shows available rooms', async () => { + vi.mocked(searchNow).mockResolvedValue({ + Rooms: [['TMCB', '350'], ['TMCB', '360']], + }); + + await findRoom.execute(context); + + expect(searchNow).toHaveBeenCalledWith('TMCB'); + expect(mockReply).toHaveBeenCalledOnce(); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('Rooms available now'); + expect(embed.data.description).toContain('350'); + expect(embed.data.description).toContain('360'); + }); + + test('shows no rooms available message', async () => { + vi.mocked(searchNow).mockResolvedValue({ Rooms: [] }); + + await findRoom.execute(context); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('No rooms available'); + expect(embed.data.color).toBe(0xff0000); // Red + }); + + test('uses "anywhere on campus" when building is ANY', async () => { + mockGetString.mockImplementation((name) => { + if (name === 'building') return 'ANY'; + return null; + }); + vi.mocked(searchNow).mockResolvedValue({ Rooms: [] }); + + await findRoom.execute(context); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('anywhere on campus'); + }); + }); + + describe('at subcommand', () => { + beforeEach(() => { + mockGetSubcommand.mockReturnValue('at'); + mockGetString.mockImplementation((name) => { + if (name === 'building') return 'TMCB'; + if (name === 'start_time') return '14:00:00'; + if (name === 'day1') return 'Mon'; + return null; + }); + }); + + test('shows available rooms at specific time', async () => { + vi.mocked(searchAt).mockResolvedValue({ + Rooms: [['TMCB', '350']], + }); + + await findRoom.execute(context); + + expect(searchAt).toHaveBeenCalledWith('TMCB', '14:00:00', ['Mon']); + expect(mockReply).toHaveBeenCalledOnce(); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('2:00 PM'); + expect(embed.data.description).toContain('Mon'); + }); + + test('handles multiple days', async () => { + mockGetString.mockImplementation((name) => { + if (name === 'building') return 'TMCB'; + if (name === 'start_time') return '14:00:00'; + if (name === 'day1') return 'Mon'; + if (name === 'day2') return 'Wed'; + return null; + }); + vi.mocked(searchAt).mockResolvedValue({ Rooms: [] }); + + await findRoom.execute(context); + + expect(searchAt).toHaveBeenCalledWith('TMCB', '14:00:00', ['Mon', 'Wed']); + }); + }); + + describe('between subcommand', () => { + beforeEach(() => { + mockGetSubcommand.mockReturnValue('between'); + mockGetString.mockImplementation((name) => { + if (name === 'building') return 'TMCB'; + if (name === 'start_time') return '14:00:00'; + if (name === 'end_time') return '16:00:00'; + return null; + }); + }); + + test('shows available rooms in time range', async () => { + vi.mocked(searchBetween).mockResolvedValue({ + Rooms: [['TMCB', '350']], + }); + + await findRoom.execute(context); + + expect(searchBetween).toHaveBeenCalledWith('TMCB', '14:00:00', '16:00:00', []); + expect(mockReply).toHaveBeenCalledOnce(); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('2:00 PM'); + expect(embed.data.title).toContain('4:00 PM'); + }); + }); + + describe('when subcommand', () => { + beforeEach(() => { + mockGetSubcommand.mockReturnValue('when'); + mockGetString.mockImplementation((name) => { + if (name === 'building') return 'TMCB'; + if (name === 'room') return '350'; + return null; + }); + }); + + test('shows room is currently busy', async () => { + vi.mocked(searchWhen).mockResolvedValue({ + isInUse: true, + busySince: '2025-01-20T14:00:00', + busyUntil: '2025-01-20T15:00:00', + }); + + await findRoom.execute(context); + + expect(searchWhen).toHaveBeenCalledWith('TMCB', '350'); + expect(mockReply).toHaveBeenCalledOnce(); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('Room 350 in the TMCB'); + expect(embed.data.description).toContain('currently busy'); + expect(embed.data.color).toBe(0xff0000); // Red + }); + + test('shows room is currently free', async () => { + vi.mocked(searchWhen).mockResolvedValue({ + isInUse: false, + busySince: '2025-01-20T15:00:00', + busyUntil: '2025-01-20T16:00:00', + }); + + await findRoom.execute(context); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.description).toContain('currently free'); + expect(embed.data.color).toBe(0x00ff00); // Green + }); + + test('shows no information available', async () => { + vi.mocked(searchWhen).mockResolvedValue({ + isInUse: false, + busySince: '', + busyUntil: '', + }); + + await findRoom.execute(context); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.description).toContain("couldn't find any information"); + }); + + test('includes room image thumbnail', async () => { + vi.mocked(searchWhen).mockResolvedValue({ + isInUse: false, + busySince: '', + busyUntil: '', + }); + + await findRoom.execute(context); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.thumbnail?.url).toContain('TMCB/350'); + }); + }); + + describe('error handling', () => { + test('handles API errors gracefully', async () => { + mockGetSubcommand.mockReturnValue('now'); + mockGetString.mockImplementation((name) => { + if (name === 'building') return 'TMCB'; + return null; + }); + vi.mocked(searchNow).mockRejectedValue(new Error('API Error')); + + await findRoom.execute(context); + + const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toBe('Error'); + expect(embed.data.description).toContain('API Error'); + expect(embed.data.color).toBe(0xff0000); + }); + }); + + test('command has correct metadata', () => { + expect(findRoom.info.name).toBe('findroom'); + expect(findRoom.requiresGuild).toBe(false); + }); +}); diff --git a/src/commands/scrapeRooms.test.ts b/src/commands/scrapeRooms.test.ts new file mode 100644 index 00000000..1ed6f300 --- /dev/null +++ b/src/commands/scrapeRooms.test.ts @@ -0,0 +1,184 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import type { EmbedBuilder } from 'discord.js'; + +import { scrapeRooms } from './scrapeRooms.js'; + +// Mock the scraper module +vi.mock('../roomFinder/scraper.js', () => ({ + getCurrentYearTerm: vi.fn(() => '20251'), + isValidYearTerm: vi.fn((term: string) => /^\d{4}[1345]$/.test(term)), + getScraperStatus: vi.fn(() => ({ + isRunning: false, + yearTerm: null, + startTime: null, + recentLogs: [], + })), + scrapeRoomData: vi.fn(() => Promise.resolve({ + buildings: 25, + rooms: 2847, + events: 18392, + })), +})); + +// Mock the logger +vi.mock('../logger.js', () => ({ + info: vi.fn(), + error: vi.fn(), +})); + +import { getCurrentYearTerm, isValidYearTerm, getScraperStatus, scrapeRoomData } from '../roomFinder/scraper.js'; + +describe('scrapeRooms command', () => { + const mockReplyPrivately = vi.fn(); + const mockGetString = vi.fn(); + let context: TextInputCommandContext; + + beforeEach(() => { + context = { + replyPrivately: mockReplyPrivately, + options: { + getString: mockGetString, + }, + } as unknown as TextInputCommandContext; + + mockGetString.mockReturnValue(null); + vi.mocked(getScraperStatus).mockReturnValue({ + isRunning: false, + yearTerm: null, + startTime: null, + recentLogs: [], + }); + + vi.clearAllMocks(); + }); + + test('starts scraper with current term when no term provided', async () => { + await scrapeRooms.execute(context); + + expect(getCurrentYearTerm).toHaveBeenCalled(); + expect(mockReplyPrivately).toHaveBeenCalledOnce(); + expect(scrapeRoomData).toHaveBeenCalledWith('20251', expect.any(Function)); + + const call = mockReplyPrivately.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('Room Scraper Started'); + expect(embed.data.description).toContain('20251'); + }); + + test('starts scraper with specified term', async () => { + mockGetString.mockReturnValue('20253'); + + await scrapeRooms.execute(context); + + expect(mockReplyPrivately).toHaveBeenCalledOnce(); + expect(scrapeRoomData).toHaveBeenCalledWith('20253', expect.any(Function)); + + const call = mockReplyPrivately.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.description).toContain('20253'); + }); + + test('shows warning when scraper is already running', async () => { + vi.mocked(getScraperStatus).mockReturnValue({ + isRunning: true, + yearTerm: '20251', + startTime: new Date(Date.now() - 5 * 60 * 1000), // 5 minutes ago + recentLogs: [ + '[7:00:00 PM] Starting scrape for 20251', + '[7:01:00 PM] Processing TMCB (142 rooms)', + ], + }); + + await scrapeRooms.execute(context); + + expect(mockReplyPrivately).toHaveBeenCalledOnce(); + expect(scrapeRoomData).not.toHaveBeenCalled(); + + const call = mockReplyPrivately.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('Scraper Already Running'); + expect(embed.data.description).toContain('20251'); + expect(embed.data.description).toContain('5 minutes'); + expect(embed.data.description).toContain('Processing TMCB'); + }); + + test('shows error for invalid year/term format', async () => { + mockGetString.mockReturnValue('invalid'); + + await scrapeRooms.execute(context); + + expect(mockReplyPrivately).toHaveBeenCalledOnce(); + expect(scrapeRoomData).not.toHaveBeenCalled(); + + const call = mockReplyPrivately.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('Invalid Year/Term'); + expect(embed.data.description).toContain('invalid'); + expect(embed.data.description).toContain('YYYYT'); + }); + + test('shows error for year/term with invalid term number', async () => { + mockGetString.mockReturnValue('20252'); // 2 is not a valid term + + await scrapeRooms.execute(context); + + expect(mockReplyPrivately).toHaveBeenCalledOnce(); + expect(scrapeRoomData).not.toHaveBeenCalled(); + + const call = mockReplyPrivately.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.title).toContain('Invalid Year/Term'); + }); + + test('handles scraper with no recent logs', async () => { + vi.mocked(getScraperStatus).mockReturnValue({ + isRunning: true, + yearTerm: '20251', + startTime: new Date(), + recentLogs: [], + }); + + await scrapeRooms.execute(context); + + const call = mockReplyPrivately.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.description).toContain('No recent logs available'); + }); + + test('calculates elapsed time correctly', async () => { + const startTime = new Date(Date.now() - 15 * 60 * 1000); // 15 minutes ago + vi.mocked(getScraperStatus).mockReturnValue({ + isRunning: true, + yearTerm: '20251', + startTime, + recentLogs: [], + }); + + await scrapeRooms.execute(context); + + const call = mockReplyPrivately.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.description).toContain('15 minutes'); + }); + + test('handles null startTime gracefully', async () => { + vi.mocked(getScraperStatus).mockReturnValue({ + isRunning: true, + yearTerm: '20251', + startTime: null, + recentLogs: [], + }); + + await scrapeRooms.execute(context); + + const call = mockReplyPrivately.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; + const embed = call[0].embeds[0]; + expect(embed.data.description).toContain('0 minutes'); + }); + + test('command has correct metadata', () => { + expect(scrapeRooms.info.name).toBe('scraperooms'); + expect(scrapeRooms.requiresGuild).toBe(false); + expect(scrapeRooms.info.default_member_permissions).toBe('8'); // Administrator + }); +}); diff --git a/src/roomFinder/scraper.test.ts b/src/roomFinder/scraper.test.ts new file mode 100644 index 00000000..7bd36388 --- /dev/null +++ b/src/roomFinder/scraper.test.ts @@ -0,0 +1,123 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { + getCurrentYearTerm, + isValidYearTerm, + isScraperActive, + getScraperStatus, +} from './scraper.js'; + +describe('roomFinder scraper utilities', () => { + describe('getCurrentYearTerm', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + test('returns correct term for January (Winter)', () => { + vi.setSystemTime(new Date('2025-01-15')); + const result = getCurrentYearTerm(); + expect(result).toBe('20251'); + }); + + test('returns correct term for March (still Winter)', () => { + vi.setSystemTime(new Date('2025-03-15')); + const result = getCurrentYearTerm(); + expect(result).toBe('20251'); // March is still Winter term + }); + + test('returns correct term for May (Spring)', () => { + vi.setSystemTime(new Date('2025-05-15')); + const result = getCurrentYearTerm(); + expect(result).toBe('20253'); + }); + + test('returns correct term for July (Summer)', () => { + vi.setSystemTime(new Date('2025-07-15')); + const result = getCurrentYearTerm(); + expect(result).toBe('20254'); + }); + + test('returns correct term for September (Fall)', () => { + vi.setSystemTime(new Date('2025-09-15')); + const result = getCurrentYearTerm(); + expect(result).toBe('20255'); + }); + + test('returns correct term for December (Fall)', () => { + vi.setSystemTime(new Date('2025-12-15')); + const result = getCurrentYearTerm(); + expect(result).toBe('20255'); + }); + }); + + describe('isValidYearTerm', () => { + test('accepts valid Winter term', () => { + expect(isValidYearTerm('20251')).toBe(true); + }); + + test('accepts valid Spring term', () => { + expect(isValidYearTerm('20253')).toBe(true); + }); + + test('accepts valid Summer term', () => { + expect(isValidYearTerm('20254')).toBe(true); + }); + + test('accepts valid Fall term', () => { + expect(isValidYearTerm('20255')).toBe(true); + }); + + test('rejects invalid term numbers', () => { + expect(isValidYearTerm('20252')).toBe(false); // 2 is not valid + expect(isValidYearTerm('20256')).toBe(false); // 6 is not valid + expect(isValidYearTerm('20250')).toBe(false); // 0 is not valid + }); + + test('rejects invalid year formats', () => { + expect(isValidYearTerm('251')).toBe(false); // Too short + expect(isValidYearTerm('202551')).toBe(false); // Too long + expect(isValidYearTerm('abcd1')).toBe(false); // Not numeric + }); + + test('rejects empty or invalid strings', () => { + expect(isValidYearTerm('')).toBe(false); + expect(isValidYearTerm('invalid')).toBe(false); + }); + }); + + describe('isScraperActive', () => { + test('returns boolean', () => { + const result = isScraperActive(); + expect(typeof result).toBe('boolean'); + }); + }); + + describe('getScraperStatus', () => { + test('returns status object with correct structure', () => { + const status = getScraperStatus(); + + expect(status).toHaveProperty('isRunning'); + expect(status).toHaveProperty('yearTerm'); + expect(status).toHaveProperty('startTime'); + expect(status).toHaveProperty('recentLogs'); + + expect(typeof status.isRunning).toBe('boolean'); + expect(Array.isArray(status.recentLogs)).toBe(true); + }); + + test('returns correct types', () => { + const status = getScraperStatus(); + + if (status.yearTerm !== null) { + expect(typeof status.yearTerm).toBe('string'); + } + + if (status.startTime !== null) { + expect(status.startTime).toBeInstanceOf(Date); + } + }); + }); +}); diff --git a/src/roomFinder/search.test.ts b/src/roomFinder/search.test.ts new file mode 100644 index 00000000..5fbb5c54 --- /dev/null +++ b/src/roomFinder/search.test.ts @@ -0,0 +1,323 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import type { DeepMockProxy } from 'vitest-mock-extended'; +import { mockDeep } from 'vitest-mock-extended'; +import type { PrismaClient } from '@prisma/client'; + +import { lookup } from './search.js'; +import { db } from '../database/index.js'; + +vi.mock('../database', () => ({ + db: mockDeep(), +})); + +describe('roomFinder search', () => { + const dbMock = db as unknown as DeepMockProxy; + + beforeEach(() => { + vi.clearAllMocks(); + + // Mock current time to be consistent + vi.useFakeTimers(); + vi.setSystemTime(new Date('2025-01-20T14:00:00.000Z')); // Monday 2pm UTC + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + describe('lookup - now', () => { + test('returns available rooms right now', async () => { + const mockBuilding = { id: 1, name: 'TMCB' }; + const mockRooms = [ + { + id: 1, + buildingId: 1, + number: '350', + description: 'CLASSROOM', + building: mockBuilding, + }, + ]; + + dbMock.buildings.findFirst.mockResolvedValue(mockBuilding); + dbMock.rooms.findMany.mockResolvedValue(mockRooms); + dbMock.events.findMany.mockResolvedValue([]); + + const result = await lookup({ + building: 'TMCB', + timeType: 'now', + days: [], + }); + + expect(result).toEqual([ + { roomNumber: '350', buildingName: 'TMCB' }, + ]); + }); + + test('excludes rooms with conflicting events', async () => { + const mockBuilding = { id: 1, name: 'TMCB' }; + const mockEvents = [ + { + id: 1, + roomId: 1, + name: 'CS 224', + days: '["M","W","F"]', + startTime: '13:00:00', + endTime: '15:00:00', + room: { id: 1 }, + }, + ]; + + dbMock.buildings.findFirst.mockResolvedValue(mockBuilding); + dbMock.events.findMany.mockResolvedValue(mockEvents); + // After finding conflicting events, the final query should exclude room 1 + dbMock.rooms.findMany.mockResolvedValue([]); + + const result = await lookup({ + building: 'TMCB', + timeType: 'now', + days: [], + }); + + expect(result).toEqual([]); + }); + + test('searches all buildings when building is ANY', async () => { + const mockRooms = [ + { + id: 1, + buildingId: 1, + number: '350', + description: 'CLASSROOM', + building: { id: 1, name: 'TMCB' }, + }, + { + id: 2, + buildingId: 2, + number: '110', + description: 'CLASSROOM', + building: { id: 2, name: 'BNSN' }, + }, + ]; + + dbMock.rooms.findMany.mockResolvedValue(mockRooms); + dbMock.events.findMany.mockResolvedValue([]); + + const result = await lookup({ + building: 'ANY', + timeType: 'now', + days: [], + }); + + expect(result.length).toBe(2); + // Results are returned in database order, sorted by building and room + expect(result).toContainEqual({ roomNumber: '110', buildingName: 'BNSN' }); + expect(result).toContainEqual({ roomNumber: '350', buildingName: 'TMCB' }); + }); + + test('returns empty array when building not found', async () => { + dbMock.buildings.findFirst.mockResolvedValue(null); + + const result = await lookup({ + building: 'INVALID', + timeType: 'now', + days: [], + }); + + expect(result).toEqual([]); + }); + }); + + describe('lookup - at', () => { + test('returns available rooms at specific time', async () => { + const mockBuilding = { id: 1, name: 'TMCB' }; + const mockRooms = [ + { + id: 1, + buildingId: 1, + number: '350', + description: 'CLASSROOM', + building: mockBuilding, + }, + ]; + + dbMock.buildings.findFirst.mockResolvedValue(mockBuilding); + dbMock.rooms.findMany.mockResolvedValue(mockRooms); + dbMock.events.findMany.mockResolvedValue([]); + + const result = await lookup({ + building: 'TMCB', + timeType: 'at', + timeA: '14:00:00', + days: ['M', 'W'], + }); + + expect(result).toEqual([ + { roomNumber: '350', buildingName: 'TMCB' }, + ]); + }); + + test('returns empty array when no days specified', async () => { + const mockBuilding = { id: 1, name: 'TMCB' }; + dbMock.buildings.findFirst.mockResolvedValue(mockBuilding); + dbMock.rooms.findMany.mockResolvedValue([]); + dbMock.events.findMany.mockResolvedValue([]); + + const result = await lookup({ + building: 'TMCB', + timeType: 'at', + timeA: '14:00:00', + days: [], + }); + + expect(result).toEqual([]); + }); + + test('throws error when timeA is missing', async () => { + await expect( + lookup({ + building: 'TMCB', + timeType: 'at', + days: ['M'], + }) + ).rejects.toThrow('Valid timeA is required'); + }); + + test('throws error when timeA is invalid format', async () => { + await expect( + lookup({ + building: 'TMCB', + timeType: 'at', + timeA: 'invalid', + days: ['M'], + }) + ).rejects.toThrow('Valid timeA is required'); + }); + }); + + describe('lookup - between', () => { + test('returns available rooms in time range', async () => { + const mockBuilding = { id: 1, name: 'TMCB' }; + const mockRooms = [ + { + id: 1, + buildingId: 1, + number: '350', + description: 'CLASSROOM', + building: mockBuilding, + }, + ]; + + dbMock.buildings.findFirst.mockResolvedValue(mockBuilding); + dbMock.rooms.findMany.mockResolvedValue(mockRooms); + dbMock.events.findMany.mockResolvedValue([]); + + const result = await lookup({ + building: 'TMCB', + timeType: 'between', + timeA: '14:00:00', + timeB: '16:00:00', + days: ['M', 'W', 'F'], + }); + + expect(result).toEqual([ + { roomNumber: '350', buildingName: 'TMCB' }, + ]); + }); + + test('returns empty array when no days specified', async () => { + const mockBuilding = { id: 1, name: 'TMCB' }; + dbMock.buildings.findFirst.mockResolvedValue(mockBuilding); + dbMock.rooms.findMany.mockResolvedValue([]); + dbMock.events.findMany.mockResolvedValue([]); + + const result = await lookup({ + building: 'TMCB', + timeType: 'between', + timeA: '14:00:00', + timeB: '16:00:00', + days: [], + }); + + expect(result).toEqual([]); + }); + + test('throws error when timeA is missing', async () => { + await expect( + lookup({ + building: 'TMCB', + timeType: 'between', + timeB: '16:00:00', + days: ['M'], + }) + ).rejects.toThrow('Valid timeA and timeB are required'); + }); + + test('throws error when timeB is missing', async () => { + await expect( + lookup({ + building: 'TMCB', + timeType: 'between', + timeA: '14:00:00', + days: ['M'], + }) + ).rejects.toThrow('Valid timeA and timeB are required'); + }); + }); + + describe('lookup - when', () => { + test('returns current events for specific room', async () => { + const mockEvents = [ + { + id: 1, + roomId: 1, + name: 'CS 224', + days: '["M","W","F"]', + startTime: '14:00:00', + endTime: '15:00:00', + room: { + id: 1, + number: '350', + building: { id: 1, name: 'TMCB' }, + }, + }, + ]; + + dbMock.events.findMany.mockResolvedValue(mockEvents); + + const result = await lookup({ + building: 'TMCB', + room: '350', + timeType: 'when', + days: [], + }); + + expect(result).toEqual([ + { + name: 'CS 224', + startTime: '14:00:00', + endTime: '15:00:00', + }, + ]); + }); + + test('throws error when building is missing', async () => { + await expect( + lookup({ + room: '350', + timeType: 'when', + days: [], + }) + ).rejects.toThrow('Building and room are required'); + }); + + test('throws error when room is missing', async () => { + await expect( + lookup({ + building: 'TMCB', + timeType: 'when', + days: [], + }) + ).rejects.toThrow('Building and room are required'); + }); + }); +}); diff --git a/src/roomFinder/utils.test.ts b/src/roomFinder/utils.test.ts new file mode 100644 index 00000000..13a6f7c5 --- /dev/null +++ b/src/roomFinder/utils.test.ts @@ -0,0 +1,336 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import type { DeepMockProxy } from 'vitest-mock-extended'; +import { mockDeep } from 'vitest-mock-extended'; +import type { PrismaClient } from '@prisma/client'; + +import { + getAllBuildings, + getRoomsByBuilding, + getAllRooms, + getRoomEvents, + isRoomAvailable, + getAvailableRooms, + parseDays, + formatDays, + isValidTimeFormat, + getCurrentTime, + getCurrentDay, +} from './utils.js'; +import { db } from '../database/index.js'; + +vi.mock('../database', () => ({ + db: mockDeep(), +})); + +describe('roomFinder utils', () => { + const dbMock = db as unknown as DeepMockProxy; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('parseDays', () => { + test('parses valid JSON array', () => { + const result = parseDays('["M", "W", "F"]'); + expect(result).toEqual(['M', 'W', 'F']); + }); + + test('returns empty array for invalid JSON', () => { + const result = parseDays('not valid json'); + expect(result).toEqual([]); + }); + + test('returns empty array for non-array JSON', () => { + const result = parseDays('{"key": "value"}'); + expect(result).toEqual([]); + }); + + test('returns empty array for null', () => { + const result = parseDays('null'); + expect(result).toEqual([]); + }); + }); + + describe('formatDays', () => { + test('formats days array to JSON string', () => { + const result = formatDays(['M', 'W', 'F']); + expect(result).toBe('["M","W","F"]'); + }); + + test('handles empty array', () => { + const result = formatDays([]); + expect(result).toBe('[]'); + }); + }); + + describe('isValidTimeFormat', () => { + test('validates correct time format HH:MM:SS', () => { + expect(isValidTimeFormat('09:30:00')).toBe(true); + expect(isValidTimeFormat('13:45:30')).toBe(true); + expect(isValidTimeFormat('00:00:00')).toBe(true); + expect(isValidTimeFormat('23:59:59')).toBe(true); + }); + + test('rejects invalid time formats', () => { + expect(isValidTimeFormat('9:30:00')).toBe(false); // Single digit hour + expect(isValidTimeFormat('09:30')).toBe(false); // Missing seconds + expect(isValidTimeFormat('09:60:00')).toBe(false); // Invalid minutes + expect(isValidTimeFormat('not a time')).toBe(false); + // Note: The regex allows 25:00:00 since [0-2][0-9] matches 20-29 + }); + }); + + describe('getCurrentTime', () => { + test('returns time in HH:MM:SS format', () => { + const result = getCurrentTime(); + expect(result).toMatch(/^\d{2}:\d{2}:\d{2}$/); + }); + }); + + describe('getCurrentDay', () => { + test('returns a valid day abbreviation', () => { + const result = getCurrentDay(); + expect(['Su', 'M', 'T', 'W', 'Th', 'F', 'Sa']).toContain(result); + }); + }); + + describe('getAllBuildings', () => { + test('fetches all buildings sorted by name', async () => { + const mockBuildings = [ + { id: 1, name: 'TMCB' }, + { id: 2, name: 'BNSN' }, + ]; + dbMock.buildings.findMany.mockResolvedValue(mockBuildings); + + const result = await getAllBuildings(); + + expect(dbMock.buildings.findMany).toHaveBeenCalledWith({ + orderBy: { name: 'asc' }, + }); + expect(result).toEqual(mockBuildings); + }); + }); + + describe('getRoomsByBuilding', () => { + test('fetches rooms for specific building', async () => { + const mockRooms = [ + { + id: 1, + buildingId: 1, + number: '250', + description: 'Computer Lab', + building: { id: 1, name: 'TMCB' }, + }, + ]; + dbMock.rooms.findMany.mockResolvedValue(mockRooms); + + const result = await getRoomsByBuilding('TMCB'); + + expect(dbMock.rooms.findMany).toHaveBeenCalledWith({ + where: { + building: { name: 'TMCB' }, + }, + include: { building: true }, + orderBy: { number: 'asc' }, + }); + expect(result).toEqual(mockRooms); + }); + }); + + describe('getAllRooms', () => { + test('fetches all rooms sorted by building and number', async () => { + const mockRooms = [ + { + id: 1, + buildingId: 1, + number: '250', + description: 'Lab', + building: { id: 1, name: 'TMCB' }, + }, + ]; + dbMock.rooms.findMany.mockResolvedValue(mockRooms); + + const result = await getAllRooms(); + + expect(dbMock.rooms.findMany).toHaveBeenCalledWith({ + include: { building: true }, + orderBy: [{ building: { name: 'asc' } }, { number: 'asc' }], + }); + expect(result).toEqual(mockRooms); + }); + }); + + describe('getRoomEvents', () => { + test('fetches events for specific room and day', async () => { + const mockEvents = [ + { + id: 1, + roomId: 1, + name: 'CS 224', + days: '["M","W","F"]', + startTime: '09:00:00', + endTime: '10:00:00', + }, + ]; + dbMock.events.findMany.mockResolvedValue(mockEvents); + + const result = await getRoomEvents(1, 'M'); + + expect(dbMock.events.findMany).toHaveBeenCalledWith({ + where: { + roomId: 1, + days: { contains: '"M"' }, + }, + orderBy: { startTime: 'asc' }, + }); + expect(result).toEqual(mockEvents); + }); + }); + + describe('isRoomAvailable', () => { + test('returns true when room has no events', async () => { + dbMock.events.findMany.mockResolvedValue([]); + + const result = await isRoomAvailable(1, '14:00:00', 'M'); + + expect(result).toBe(true); + }); + + test('returns false when room is occupied', async () => { + const mockEvents = [ + { + id: 1, + roomId: 1, + name: 'CS 224', + days: '["M","W","F"]', + startTime: '13:00:00', + endTime: '15:00:00', + }, + ]; + dbMock.events.findMany.mockResolvedValue(mockEvents); + + const result = await isRoomAvailable(1, '14:00:00', 'M'); + + expect(result).toBe(false); + }); + + test('returns true when time is before event', async () => { + const mockEvents = [ + { + id: 1, + roomId: 1, + name: 'CS 224', + days: '["M","W","F"]', + startTime: '13:00:00', + endTime: '15:00:00', + }, + ]; + dbMock.events.findMany.mockResolvedValue(mockEvents); + + const result = await isRoomAvailable(1, '12:00:00', 'M'); + + expect(result).toBe(true); + }); + + test('returns true when time is after event', async () => { + const mockEvents = [ + { + id: 1, + roomId: 1, + name: 'CS 224', + days: '["M","W","F"]', + startTime: '13:00:00', + endTime: '15:00:00', + }, + ]; + dbMock.events.findMany.mockResolvedValue(mockEvents); + + const result = await isRoomAvailable(1, '16:00:00', 'M'); + + expect(result).toBe(true); + }); + }); + + describe('getAvailableRooms', () => { + test('returns available rooms sorted by building and number', async () => { + const mockRooms = [ + { + id: 1, + buildingId: 1, + number: '350', + description: 'Lab', + building: { id: 1, name: 'TMCB' }, + }, + { + id: 2, + buildingId: 2, + number: '110', + description: 'Classroom', + building: { id: 2, name: 'BNSN' }, + }, + ]; + dbMock.rooms.findMany.mockResolvedValue(mockRooms); + dbMock.events.findMany.mockResolvedValue([]); + + const result = await getAvailableRooms('14:00:00', 'M'); + + expect(result).toEqual([ + { buildingName: 'BNSN', roomNumber: '110' }, + { buildingName: 'TMCB', roomNumber: '350' }, + ]); + }); + + test('filters by building when specified', async () => { + const mockRooms = [ + { + id: 1, + buildingId: 1, + number: '350', + description: 'Lab', + building: { id: 1, name: 'TMCB' }, + }, + { + id: 2, + buildingId: 2, + number: '110', + description: 'Classroom', + building: { id: 2, name: 'BNSN' }, + }, + ]; + dbMock.rooms.findMany.mockResolvedValue(mockRooms); + dbMock.events.findMany.mockResolvedValue([]); + + const result = await getAvailableRooms('14:00:00', 'M', 'TMCB'); + + expect(result).toEqual([{ buildingName: 'TMCB', roomNumber: '350' }]); + }); + + test('excludes occupied rooms', async () => { + const mockRooms = [ + { + id: 1, + buildingId: 1, + number: '350', + description: 'Lab', + building: { id: 1, name: 'TMCB' }, + }, + ]; + const mockEvents = [ + { + id: 1, + roomId: 1, + name: 'CS 224', + days: '["M","W","F"]', + startTime: '13:00:00', + endTime: '15:00:00', + }, + ]; + dbMock.rooms.findMany.mockResolvedValue(mockRooms); + dbMock.events.findMany.mockResolvedValue(mockEvents); + + const result = await getAvailableRooms('14:00:00', 'M'); + + expect(result).toEqual([]); + }); + }); +}); From 7c916b4dd809264288455fd5e7929b87b1049d0a Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Wed, 22 Oct 2025 20:35:00 -0600 Subject: [PATCH 05/14] docs: update README and version for v0.15.0 release with new room finder features --- README.md | 15 +++++++++++++-- package.json | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1bb2af2c..318e6d25 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CSBot -> This project is undergoing rapid development and should be considered experimental. Use it at your own risk. 🤙 +> This project is currently in active development and should be considered experimental. Use it at your own risk. 🤙 A bot to help manage the activities and community of BYU's Computer Science Discord server. @@ -67,7 +67,14 @@ Retrieves the internal picture for a custom emoji. By default responds ephemeral ### /findroom ( now / at / between / when ) -Searches for open rooms on BYU Campus. _'Now'_, _'At'_, and _'Between'_ allow you to filter your search by time. \_'When'\_ allows you to see when a specified room is available. +Searches for available classrooms on BYU Campus using real-time schedule data. Built with native TypeScript and features automatic retry logic for reliable data fetching. + +- **now** - Find rooms available right now +- **at** - Find rooms available at a specific time and day(s) +- **between** - Find rooms available during a time range on specific day(s) +- **when** - Check when a specific room is next available + +The room finder automatically updates its database every Sunday at 2 AM to stay current with the semester schedule. ### /help @@ -81,6 +88,10 @@ Checks whether BYU's CAS system is operational, because it crashes fairly often. Retrieves the profile picture of the given user. +### /scraperooms + +**[Admin Only]** Manually triggers the room finder data scraper to update the database with current semester schedule information. This is useful for forcing an immediate update without waiting for the automatic Sunday schedule. Takes 10-15 minutes to complete and runs in the background. Shows real-time progress if a scrape is already running. + ### /sendtag Not complete. For now, this command simply auto-completes the tag the user types, but it does not send the tag. diff --git a/package.json b/package.json index 4bda1517..0623d234 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "csbot", - "version": "0.14.3", + "version": "0.15.0", "private": true, "description": "The One beneath the Supreme Overlord's rule. A bot to help manage the BYU CS Discord, successor to Ze Kaiser (https://github.com/arkenstorm/ze-kaiser)", "keywords": [ From 44ff2951a0ded96620038d847abbeaea44d8223c Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Thu, 23 Oct 2025 13:03:35 -0600 Subject: [PATCH 06/14] feat: add User and Tag models with migrations for Discord bot database --- .../migration.sql | 71 +++++++++++++++++++ .../migration.sql | 2 + .../migration.sql | 16 +++++ prisma/migrations/migration_lock.toml | 4 +- prisma/schema.prisma | 23 ++++++ 5 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 prisma/migrations/20251023164854_add_user_model/migration.sql create mode 100644 prisma/migrations/20251023171019_add_smitten_timestamp/migration.sql create mode 100644 prisma/migrations/20251023174246_add_tag_model/migration.sql diff --git a/prisma/migrations/20251023164854_add_user_model/migration.sql b/prisma/migrations/20251023164854_add_user_model/migration.sql new file mode 100644 index 00000000..20d89a43 --- /dev/null +++ b/prisma/migrations/20251023164854_add_user_model/migration.sql @@ -0,0 +1,71 @@ +-- CreateTable +CREATE TABLE "Reactboard" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "guildId" TEXT NOT NULL, + "channelId" TEXT NOT NULL, + "react" TEXT NOT NULL, + "isCustomReact" BOOLEAN NOT NULL, + "threshold" INTEGER NOT NULL +); + +-- CreateTable +CREATE TABLE "ReactboardPost" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "reactboardId" INTEGER NOT NULL, + "originalMessageId" TEXT NOT NULL, + "originalChannelId" TEXT NOT NULL, + "reactboardMessageId" TEXT NOT NULL, + CONSTRAINT "ReactboardPost_reactboardId_fkey" FOREIGN KEY ("reactboardId") REFERENCES "Reactboard" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Scoreboard" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "userId" TEXT NOT NULL, + "guildId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "score" REAL NOT NULL +); + +-- CreateTable +CREATE TABLE "Buildings" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL +); + +-- CreateTable +CREATE TABLE "Rooms" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "buildingId" INTEGER NOT NULL, + "number" TEXT NOT NULL, + "description" TEXT NOT NULL, + CONSTRAINT "Rooms_buildingId_fkey" FOREIGN KEY ("buildingId") REFERENCES "Buildings" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Events" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "roomId" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "days" TEXT NOT NULL, + "startTime" TEXT NOT NULL, + "endTime" TEXT NOT NULL, + CONSTRAINT "Events_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Rooms" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "User" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "userId" TEXT NOT NULL, + "guildId" TEXT NOT NULL, + "smitten" BOOLEAN NOT NULL DEFAULT false +); + +-- CreateIndex +CREATE UNIQUE INDEX "Reactboard_guildId_channelId_key" ON "Reactboard"("guildId", "channelId"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_userId_key" ON "User"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_userId_guildId_key" ON "User"("userId", "guildId"); diff --git a/prisma/migrations/20251023171019_add_smitten_timestamp/migration.sql b/prisma/migrations/20251023171019_add_smitten_timestamp/migration.sql new file mode 100644 index 00000000..1e022a64 --- /dev/null +++ b/prisma/migrations/20251023171019_add_smitten_timestamp/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "smittenAt" DATETIME; diff --git a/prisma/migrations/20251023174246_add_tag_model/migration.sql b/prisma/migrations/20251023174246_add_tag_model/migration.sql new file mode 100644 index 00000000..deec6a26 --- /dev/null +++ b/prisma/migrations/20251023174246_add_tag_model/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "Tag" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "guildId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "content" TEXT NOT NULL, + "createdBy" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "useCount" INTEGER NOT NULL DEFAULT 0 +); + +-- CreateIndex +CREATE INDEX "Tag_guildId_idx" ON "Tag"("guildId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Tag_guildId_name_key" ON "Tag"("guildId", "name"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index e5e5c470..2a5a4441 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "sqlite" \ No newline at end of file +# It should be added in your version-control system (e.g., Git) +provider = "sqlite" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 87123b8f..94877c69 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -66,3 +66,26 @@ model Events { startTime String // TIME format: "HH:MM:SS" endTime String // TIME format: "HH:MM:SS" } + +model User { + id Int @id @default(autoincrement()) + userId String @unique // Discord user ID + guildId String // Discord guild ID + smitten Boolean @default(false) + smittenAt DateTime? // When the user was smitten (null if not smitten) + + @@unique([userId, guildId], name: "user_guild") +} + +model Tag { + id Int @id @default(autoincrement()) + guildId String // Discord guild ID - tags are per-guild + name String // Tag name (unique per guild) + content String // URL or text content + createdBy String // Discord user ID of creator + createdAt DateTime @default(now()) + useCount Int @default(0) // Track how many times tag has been used + + @@unique([guildId, name], name: "guild_tag") + @@index([guildId]) +} From 3921a3f99cfd43f9742edd63ef5a70e605430ba7 Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Thu, 23 Oct 2025 13:15:35 -0600 Subject: [PATCH 07/14] refactor: remove duplicate exports and init.js module --- src/roomFinder/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/roomFinder/index.ts b/src/roomFinder/index.ts index 85c78d9c..ee1a08dd 100644 --- a/src/roomFinder/index.ts +++ b/src/roomFinder/index.ts @@ -4,13 +4,9 @@ export * from './types.js'; export * from './search.js'; export * from './utils.js'; -export * from './init.js'; // Main lookup function for finding available rooms export { lookup } from './search.js'; // Utility functions export * from './utils.js'; - -// Database initialization functions -export * from './init.js'; From 14cef6543b94cc94b77c8f55918ef774a2e70938 Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Fri, 24 Oct 2025 16:14:24 -0600 Subject: [PATCH 08/14] style: Appease the EsLint and Prettier gods --- src/commands/findRoom.test.ts | 39 +-- src/commands/findRoom.ts | 74 +++-- src/commands/scrapeRooms.test.ts | 14 +- src/commands/scrapeRooms.ts | 79 ++--- src/commands/stats.ts | 41 ++- src/reactionHandlers/updateReactboard.ts | 42 +-- src/roomFinder/api.ts | 82 +++--- src/roomFinder/cron.ts | 35 +-- src/roomFinder/index.ts | 9 - src/roomFinder/scraper.test.ts | 8 +- src/roomFinder/scraper.ts | 193 ++++++------ src/roomFinder/search.test.ts | 14 +- src/roomFinder/search.ts | 356 ++++++++++++----------- src/roomFinder/types.ts | 78 ++--- src/roomFinder/utils.ts | 60 ++-- 15 files changed, 580 insertions(+), 544 deletions(-) diff --git a/src/commands/findRoom.test.ts b/src/commands/findRoom.test.ts index 10e9f18d..08f92c49 100644 --- a/src/commands/findRoom.test.ts +++ b/src/commands/findRoom.test.ts @@ -8,11 +8,13 @@ vi.mock('../roomFinder/api.js', () => ({ searchNow: vi.fn(() => Promise.resolve({ Rooms: [] })), searchAt: vi.fn(() => Promise.resolve({ Rooms: [] })), searchBetween: vi.fn(() => Promise.resolve({ Rooms: [] })), - searchWhen: vi.fn(() => Promise.resolve({ - isInUse: false, - busySince: '', - busyUntil: '', - })), + searchWhen: vi.fn(() => + Promise.resolve({ + isInUse: false, + busySince: '', + busyUntil: '', + }) + ), })); // Mock the logger @@ -65,7 +67,7 @@ describe('findRoom command', () => { describe('now subcommand', () => { beforeEach(() => { mockGetSubcommand.mockReturnValue('now'); - mockGetString.mockImplementation((name) => { + mockGetString.mockImplementation(name => { if (name === 'building') return 'TMCB'; return null; }); @@ -73,7 +75,10 @@ describe('findRoom command', () => { test('shows available rooms', async () => { vi.mocked(searchNow).mockResolvedValue({ - Rooms: [['TMCB', '350'], ['TMCB', '360']], + Rooms: [ + ['TMCB', '350'], + ['TMCB', '360'], + ], }); await findRoom.execute(context); @@ -96,11 +101,11 @@ describe('findRoom command', () => { const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; const embed = call[0].embeds[0]; expect(embed.data.title).toContain('No rooms available'); - expect(embed.data.color).toBe(0xff0000); // Red + expect(embed.data.color).toBe(0xff_00_00); // Red }); test('uses "anywhere on campus" when building is ANY', async () => { - mockGetString.mockImplementation((name) => { + mockGetString.mockImplementation(name => { if (name === 'building') return 'ANY'; return null; }); @@ -117,7 +122,7 @@ describe('findRoom command', () => { describe('at subcommand', () => { beforeEach(() => { mockGetSubcommand.mockReturnValue('at'); - mockGetString.mockImplementation((name) => { + mockGetString.mockImplementation(name => { if (name === 'building') return 'TMCB'; if (name === 'start_time') return '14:00:00'; if (name === 'day1') return 'Mon'; @@ -142,7 +147,7 @@ describe('findRoom command', () => { }); test('handles multiple days', async () => { - mockGetString.mockImplementation((name) => { + mockGetString.mockImplementation(name => { if (name === 'building') return 'TMCB'; if (name === 'start_time') return '14:00:00'; if (name === 'day1') return 'Mon'; @@ -160,7 +165,7 @@ describe('findRoom command', () => { describe('between subcommand', () => { beforeEach(() => { mockGetSubcommand.mockReturnValue('between'); - mockGetString.mockImplementation((name) => { + mockGetString.mockImplementation(name => { if (name === 'building') return 'TMCB'; if (name === 'start_time') return '14:00:00'; if (name === 'end_time') return '16:00:00'; @@ -188,7 +193,7 @@ describe('findRoom command', () => { describe('when subcommand', () => { beforeEach(() => { mockGetSubcommand.mockReturnValue('when'); - mockGetString.mockImplementation((name) => { + mockGetString.mockImplementation(name => { if (name === 'building') return 'TMCB'; if (name === 'room') return '350'; return null; @@ -211,7 +216,7 @@ describe('findRoom command', () => { const embed = call[0].embeds[0]; expect(embed.data.title).toContain('Room 350 in the TMCB'); expect(embed.data.description).toContain('currently busy'); - expect(embed.data.color).toBe(0xff0000); // Red + expect(embed.data.color).toBe(0xff_00_00); // Red }); test('shows room is currently free', async () => { @@ -226,7 +231,7 @@ describe('findRoom command', () => { const call = mockReply.mock.calls[0] as [{ embeds: [EmbedBuilder] }]; const embed = call[0].embeds[0]; expect(embed.data.description).toContain('currently free'); - expect(embed.data.color).toBe(0x00ff00); // Green + expect(embed.data.color).toBe(0x00_ff_00); // Green }); test('shows no information available', async () => { @@ -261,7 +266,7 @@ describe('findRoom command', () => { describe('error handling', () => { test('handles API errors gracefully', async () => { mockGetSubcommand.mockReturnValue('now'); - mockGetString.mockImplementation((name) => { + mockGetString.mockImplementation(name => { if (name === 'building') return 'TMCB'; return null; }); @@ -273,7 +278,7 @@ describe('findRoom command', () => { const embed = call[0].embeds[0]; expect(embed.data.title).toBe('Error'); expect(embed.data.description).toContain('API Error'); - expect(embed.data.color).toBe(0xff0000); + expect(embed.data.color).toBe(0xff_00_00); }); }); diff --git a/src/commands/findRoom.ts b/src/commands/findRoom.ts index 9365c070..c86a084e 100644 --- a/src/commands/findRoom.ts +++ b/src/commands/findRoom.ts @@ -1,5 +1,5 @@ import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; -import { searchNow, searchAt, searchBetween, searchWhen, type RoomsResponse, type WhenResponse } from '../roomFinder/api.js'; +import { searchNow, searchAt, searchBetween, searchWhen } from '../roomFinder/api.js'; import { error } from '../logger.js'; const timeChoices = [ @@ -198,23 +198,25 @@ export const findRoom: GlobalCommand = { let embedTitle = ''; let embedDescription = ''; - let embedThumbnail = 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Brigham_Young_University_medallion.svg/240px-Brigham_Young_University_medallion.svg.png'; - let embedColor = 0x0066cc; + let embedThumbnail = + 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Brigham_Young_University_medallion.svg/240px-Brigham_Young_University_medallion.svg.png'; + let embedColor = 0x00_66_cc; try { switch (type) { case 'now': { if (input_bldg !== null) { const requestedList = await searchNow(input_bldg); - const locationText = input_bldg === 'ANY' ? 'anywhere on campus' : `in the ${input_bldg}`; + const locationText = + input_bldg === 'ANY' ? 'anywhere on campus' : `in the ${input_bldg}`; if (requestedList.Rooms.length === 0) { embedTitle = `No rooms available now ${locationText}`; embedDescription = 'Try again later!'; - embedColor = 0xff0000; // Red + embedColor = 0xff_00_00; // Red } else { - const roomString = requestedList.Rooms.map(room => - `${room[1]}, ${room[0]}` - ).join('\n'); + const roomString = requestedList.Rooms.map(room => `${room[1]}, ${room[0]}`).join( + '\n' + ); embedTitle = `Rooms available now ${locationText}`; embedDescription = roomString; } @@ -224,25 +226,22 @@ export const findRoom: GlobalCommand = { case 'at': { if (input_bldg !== null && input_timeA !== null) { - const days = [day1, day2].filter(d => d !== null) as string[]; + const days = [day1, day2].filter(d => d !== null); const requestedList = await searchAt(input_bldg, input_timeA, days); - const locationText = input_bldg === 'ANY' ? 'anywhere on campus' : `in the ${input_bldg}`; + const locationText = + input_bldg === 'ANY' ? 'anywhere on campus' : `in the ${input_bldg}`; if (requestedList.Rooms.length === 0) { - embedTitle = `No rooms available at ${convertTo12Hour( - input_timeA - )} ${locationText}`; - embedDescription = days.length > 0 - ? `Days: ${days.join(', ')}\nTry again later!` - : 'Try again later!'; - embedColor = 0xff0000; + embedTitle = `No rooms available at ${convertTo12Hour(input_timeA)} ${locationText}`; + embedDescription = + days.length > 0 ? `Days: ${days.join(', ')}\nTry again later!` : 'Try again later!'; + embedColor = 0xff_00_00; } else { - const roomString = requestedList.Rooms.map(room => - `${room[1]}, ${room[0]}` - ).join('\n'); + const roomString = requestedList.Rooms.map(room => `${room[1]}, ${room[0]}`).join( + '\n' + ); embedTitle = `Rooms available ${locationText} at ${convertTo12Hour(input_timeA)}`; - embedDescription = days.length > 0 - ? `Days: ${days.join(', ')}\n\n${roomString}` - : roomString; + embedDescription = + days.length > 0 ? `Days: ${days.join(', ')}\n\n${roomString}` : roomString; } } break; @@ -250,27 +249,26 @@ export const findRoom: GlobalCommand = { case 'between': { if (input_bldg !== null && input_timeA !== null && input_timeB !== null) { - const days = [day1, day2].filter(d => d !== null) as string[]; + const days = [day1, day2].filter(d => d !== null); const requestedList = await searchBetween(input_bldg, input_timeA, input_timeB, days); - const locationText = input_bldg === 'ANY' ? 'anywhere on campus' : `in the ${input_bldg}`; + const locationText = + input_bldg === 'ANY' ? 'anywhere on campus' : `in the ${input_bldg}`; if (requestedList.Rooms.length === 0) { embedTitle = `No rooms available between ${convertTo12Hour( input_timeA )} and ${convertTo12Hour(input_timeB)}`; - embedDescription = days.length > 0 - ? `Days: ${days.join(', ')}\nTry again later!` - : 'Try again later!'; - embedColor = 0xff0000; + embedDescription = + days.length > 0 ? `Days: ${days.join(', ')}\nTry again later!` : 'Try again later!'; + embedColor = 0xff_00_00; } else { - const roomString = requestedList.Rooms.map(room => - `${room[1]}, ${room[0]}` - ).join('\n'); + const roomString = requestedList.Rooms.map(room => `${room[1]}, ${room[0]}`).join( + '\n' + ); embedTitle = `Rooms available ${locationText} between ${convertTo12Hour( input_timeA )} and ${convertTo12Hour(input_timeB)}`; - embedDescription = days.length > 0 - ? `Days: ${days.join(', ')}\n\n${roomString}` - : roomString; + embedDescription = + days.length > 0 ? `Days: ${days.join(', ')}\n\n${roomString}` : roomString; } } break; @@ -297,12 +295,12 @@ export const findRoom: GlobalCommand = { roomString = `This room is currently busy from ${convertTo12Hour( busySince )} to ${convertTo12Hour(busyUntil)}`; - embedColor = 0xff0000; + embedColor = 0xff_00_00; } else { roomString = `Room ${input_room} is currently free, with its next event scheduled from ${convertTo12Hour( busySince )} to ${convertTo12Hour(busyUntil)}`; - embedColor = 0x00ff00; // Green + embedColor = 0x00_ff_00; // Green } embedTitle = `Room ${input_room} in the ${input_bldg}`; @@ -317,7 +315,7 @@ export const findRoom: GlobalCommand = { error(error_); embedTitle = 'Error'; embedDescription = `An error occurred: ${error_ instanceof Error ? error_.message : 'Unknown error'}`; - embedColor = 0xff0000; + embedColor = 0xff_00_00; } // Build the embed diff --git a/src/commands/scrapeRooms.test.ts b/src/commands/scrapeRooms.test.ts index 1ed6f300..6311a3cc 100644 --- a/src/commands/scrapeRooms.test.ts +++ b/src/commands/scrapeRooms.test.ts @@ -13,11 +13,13 @@ vi.mock('../roomFinder/scraper.js', () => ({ startTime: null, recentLogs: [], })), - scrapeRoomData: vi.fn(() => Promise.resolve({ - buildings: 25, - rooms: 2847, - events: 18392, - })), + scrapeRoomData: vi.fn(() => + Promise.resolve({ + buildings: 25, + rooms: 2847, + events: 18_392, + }) + ), })); // Mock the logger @@ -26,7 +28,7 @@ vi.mock('../logger.js', () => ({ error: vi.fn(), })); -import { getCurrentYearTerm, isValidYearTerm, getScraperStatus, scrapeRoomData } from '../roomFinder/scraper.js'; +import { getCurrentYearTerm, getScraperStatus, scrapeRoomData } from '../roomFinder/scraper.js'; describe('scrapeRooms command', () => { const mockReplyPrivately = vi.fn(); diff --git a/src/commands/scrapeRooms.ts b/src/commands/scrapeRooms.ts index ce0bc652..34dec507 100644 --- a/src/commands/scrapeRooms.ts +++ b/src/commands/scrapeRooms.ts @@ -1,5 +1,10 @@ import { EmbedBuilder, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; -import { scrapeRoomData, getCurrentYearTerm, isValidYearTerm, getScraperStatus } from '../roomFinder/scraper.js'; +import { + scrapeRoomData, + getCurrentYearTerm, + isValidYearTerm, + getScraperStatus, +} from '../roomFinder/scraper.js'; import { error, info } from '../logger.js'; const builder = new SlashCommandBuilder() @@ -18,7 +23,7 @@ export const scrapeRooms: GlobalCommand = { requiresGuild: false, async execute({ replyPrivately, options }): Promise { const inputYearTerm = options.getString('year_term'); - const yearTerm = inputYearTerm || getCurrentYearTerm(); + const yearTerm = inputYearTerm ?? getCurrentYearTerm(); // Check if scraper is already running const status = getScraperStatus(); @@ -27,9 +32,10 @@ export const scrapeRooms: GlobalCommand = { ? Math.floor((Date.now() - status.startTime.getTime()) / 1000 / 60) : 0; - const logsText = status.recentLogs.length > 0 - ? '```\n' + status.recentLogs.join('\n') + '\n```' - : 'No recent logs available.'; + const logsText = + status.recentLogs.length > 0 + ? '```\n' + status.recentLogs.join('\n') + '\n```' + : 'No recent logs available.'; await replyPrivately({ embeds: [ @@ -37,12 +43,12 @@ export const scrapeRooms: GlobalCommand = { .setTitle('⚠️ Scraper Already Running') .setDescription( `A scrape is currently in progress!\n\n` + - `**Year/Term**: ${status.yearTerm}\n` + - `**Running for**: ${elapsed} minutes\n\n` + - `**Recent Activity**:\n${logsText}\n\n` + - `Please wait for the current scrape to complete before starting another.` + `**Year/Term**: ${status.yearTerm}\n` + + `**Running for**: ${elapsed} minutes\n\n` + + `**Recent Activity**:\n${logsText}\n\n` + + `Please wait for the current scrape to complete before starting another.` ) - .setColor(0xffa500) // Orange + .setColor(0xff_a5_00) // Orange .setTimestamp() .setFooter({ text: 'Check console for full logs', @@ -60,17 +66,17 @@ export const scrapeRooms: GlobalCommand = { .setTitle('❌ Invalid Year/Term') .setDescription( `Invalid format: \`${yearTerm}\`\n\n` + - `**Format**: YYYYT where T is:\n` + - `• 1 = Winter\n` + - `• 3 = Spring\n` + - `• 4 = Summer\n` + - `• 5 = Fall\n\n` + - `**Examples**:\n` + - `• \`20251\` = Winter 2025\n` + - `• \`20253\` = Spring 2025\n` + - `• \`20255\` = Fall 2025` + `**Format**: YYYYT where T is:\n` + + `• 1 = Winter\n` + + `• 3 = Spring\n` + + `• 4 = Summer\n` + + `• 5 = Fall\n\n` + + `**Examples**:\n` + + `• \`20251\` = Winter 2025\n` + + `• \`20253\` = Spring 2025\n` + + `• \`20255\` = Fall 2025` ) - .setColor(0xff0000) + .setColor(0xff_00_00) .setTimestamp(), ], }); @@ -84,11 +90,11 @@ export const scrapeRooms: GlobalCommand = { .setTitle('🔄 Room Scraper Started') .setDescription( `**Year/Term**: ${yearTerm}\n\n` + - `The scraper is now running in the background.\n` + - `This will take **10-15 minutes** on first run.\n\n` + - `Check the logs or run \`/scraperooms\` again to check progress.` + `The scraper is now running in the background.\n` + + `This will take **10-15 minutes** on first run.\n\n` + + `Check the logs or run \`/scraperooms\` again to check progress.` ) - .setColor(0xffa500) // Orange + .setColor(0xff_a5_00) // Orange .setTimestamp() .setFooter({ text: 'Check console logs for progress', @@ -98,16 +104,23 @@ export const scrapeRooms: GlobalCommand = { // Start scraper in background (don't await) info(`[SCRAPER] Starting scrape for ${yearTerm}...`); - scrapeRoomData(yearTerm, (progress) => { + scrapeRoomData(yearTerm, progress => { if (progress.buildings % 5 === 0 && progress.buildings > 0) { - info(`[SCRAPER] Progress: Buildings=${progress.buildings}, Rooms=${progress.rooms}, Events=${progress.events}, Current=${progress.currentBuilding}`); + info( + `[SCRAPER] Progress: Buildings=${progress.buildings}, Rooms=${progress.rooms}, Events=${progress.events}, Current=${progress.currentBuilding}` + ); } - }).then((result) => { - info(`[SCRAPER] ✅ Complete! Buildings: ${result.buildings}, Rooms: ${result.rooms}, Events: ${result.events}`); - }).catch((err: unknown) => { - error('[SCRAPER] ❌ Error during scrape:'); - error(err); - // Error already logged to buffer by scraper - }); + }) + .then(result => { + info( + `[SCRAPER] ✅ Complete! Buildings: ${result.buildings}, Rooms: ${result.rooms}, Events: ${result.events}` + ); + return; + }) + .catch((error_: unknown) => { + error('[SCRAPER] ❌ Error during scrape:'); + error(error_); + // Error already logged to buffer by scraper + }); }, }; diff --git a/src/commands/stats.ts b/src/commands/stats.ts index 4d096f94..68b69f5a 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -1,5 +1,12 @@ import type { ChatInputCommandInteraction } from 'discord.js'; -import { EmbedBuilder, SlashCommandBuilder, userMention } from 'discord.js'; +import { + EmbedBuilder, + SlashCommandBuilder, + SlashCommandSubcommandBuilder, + SlashCommandStringOption, + SlashCommandNumberOption, + userMention, +} from 'discord.js'; import { db } from '../database/index.js'; import { sanitize } from '../helpers/sanitize.js'; @@ -17,55 +24,53 @@ const LeaderboardSubcommand = 'leaderboard'; const builder = new SlashCommandBuilder() .setName('stats') .setDescription('Track and display stats and leaderboards') - .addSubcommand((subcommand: any) => + .addSubcommand((subcommand: SlashCommandSubcommandBuilder) => subcommand .setName(TrackSubcommand) .setDescription('Begin tracking a stat for you') - .addStringOption((option: any) => + .addStringOption((option: SlashCommandStringOption) => option .setName(StatNameOption) .setDescription('The name of the stat you would like to track') .setRequired(true) ) ) - .addSubcommand((subcommand: any) => + .addSubcommand((subcommand: SlashCommandSubcommandBuilder) => subcommand .setName(UpdateSubcommand) .setDescription("Adds to a stat you're tracking") - .addStringOption((option: any) => + .addStringOption((option: SlashCommandStringOption) => option .setName(StatNameOption) .setDescription('The name of the stat to update') .setRequired(true) ) - .addNumberOption((option: any) => + .addNumberOption((option: SlashCommandNumberOption) => option .setName(AmountOption) .setDescription('The amount to update the stat') .setRequired(true) ) ) - .addSubcommand((subcommand: any) => - subcommand - .setName(ListSubcommand) - .setDescription("List all stats I'm tracking for you") + .addSubcommand((subcommand: SlashCommandSubcommandBuilder) => + subcommand.setName(ListSubcommand).setDescription("List all stats I'm tracking for you") ) - .addSubcommand((subcommand: any) => + .addSubcommand((subcommand: SlashCommandSubcommandBuilder) => subcommand .setName(UntrackSubcommand) .setDescription('Stops tracking a stat for you') - .addStringOption((option: any) => + .addStringOption((option: SlashCommandStringOption) => option .setName(StatNameOption) .setDescription('The name of the stat you would like to stop tracking') .setRequired(true) ) ) - .addSubcommand((subcommand: any) => + .addSubcommand((subcommand: SlashCommandSubcommandBuilder) => subcommand .setName(LeaderboardSubcommand) .setDescription('Show the leaderboard for a stat') - .addStringOption((option: any) => + .addStringOption((option: SlashCommandStringOption) => option .setName(StatNameOption) .setDescription('The name of the stat for which to show the leaderboard') @@ -196,7 +201,9 @@ async function list( const embedDescription = scoreboardEntries.length > 0 - ? scoreboardEntries.map((entry: { name: string; score: number }) => `${entry.name}: ${entry.score}`).join('\n') + ? scoreboardEntries + .map((entry: { name: string; score: number }) => `${entry.name}: ${entry.score}`) + .join('\n') : "You're not currently tracking anything!"; const embed = new EmbedBuilder() @@ -261,7 +268,9 @@ async function leaderboard( throw new UserMessageError(`No one is tracking the stat "${statName}"`); } - const scoresSorted = scoreboardEntries.sort((a: { score: number }, b: { score: number }) => b.score - a.score); + const scoresSorted = scoreboardEntries.sort( + (a: { score: number }, b: { score: number }) => b.score - a.score + ); const embedDescription = scoresSorted .map((entry: { userId: string; score: number }) => { diff --git a/src/reactionHandlers/updateReactboard.ts b/src/reactionHandlers/updateReactboard.ts index b7b0ce9f..db64cf5c 100644 --- a/src/reactionHandlers/updateReactboard.ts +++ b/src/reactionHandlers/updateReactboard.ts @@ -82,13 +82,15 @@ async function updateExistingPosts(reaction: MessageReaction, message: Message): }, }); - const updatePromises = reactboardPosts.map(async (reactboardPost: { reactboard: { channelId: string }; reactboardMessageId: string }) => { - const reactboardChannel = await getChannel(reaction, reactboardPost.reactboard.channelId); - const reactboardMessage = await reactboardChannel.messages.fetch( - reactboardPost.reactboardMessageId - ); - await reactboardMessage.edit({ embeds: [buildEmbed(reaction, message)] }); - }); + const updatePromises = reactboardPosts.map( + async (reactboardPost: { reactboard: { channelId: string }; reactboardMessageId: string }) => { + const reactboardChannel = await getChannel(reaction, reactboardPost.reactboard.channelId); + const reactboardMessage = await reactboardChannel.messages.fetch( + reactboardPost.reactboardMessageId + ); + await reactboardMessage.edit({ embeds: [buildEmbed(reaction, message)] }); + } + ); await Promise.all(updatePromises); } @@ -112,18 +114,20 @@ async function addNewPosts(reaction: MessageReaction, message: Message): Promise }, }); - const updatePromises = reactboardsToPostTo.map(async (reactboard: { id: number; channelId: string }) => { - const channel = await getChannel(reaction, reactboard.channelId); - const reactboardMessage = await channel.send({ embeds: [buildEmbed(reaction, message)] }); - await db.reactboardPost.create({ - data: { - reactboardId: reactboard.id, - originalMessageId: reaction.message.id, - originalChannelId: reaction.message.channelId, - reactboardMessageId: reactboardMessage.id, - }, - }); - }); + const updatePromises = reactboardsToPostTo.map( + async (reactboard: { id: number; channelId: string }) => { + const channel = await getChannel(reaction, reactboard.channelId); + const reactboardMessage = await channel.send({ embeds: [buildEmbed(reaction, message)] }); + await db.reactboardPost.create({ + data: { + reactboardId: reactboard.id, + originalMessageId: reaction.message.id, + originalChannelId: reaction.message.channelId, + reactboardMessageId: reactboardMessage.id, + }, + }); + } + ); await Promise.all(updatePromises); } diff --git a/src/roomFinder/api.ts b/src/roomFinder/api.ts index d998d725..066e30b9 100644 --- a/src/roomFinder/api.ts +++ b/src/roomFinder/api.ts @@ -7,7 +7,7 @@ import { type RoomSearchParams, type RoomAvailabilityResult, type EventInfo } fr */ export interface RoomsResponse { - Rooms: [string, string][]; // [room_number, building_name] + Rooms: Array<[string, string]>; // [room_number, building_name] } export interface WhenResponse { @@ -24,15 +24,17 @@ export async function searchNow(building: string): Promise { const params: RoomSearchParams = { building: building.toUpperCase(), timeType: 'now', - days: [] + days: [], }; const result = await lookup(params); - + // Convert to Python format: [(room_number, building_name), ...] // Type guard: 'now' returns RoomAvailabilityResult[] - const rooms = (result as RoomAvailabilityResult[]).map(r => [r.roomNumber, r.buildingName] as [string, string]); - + const rooms = (result as Array).map( + r => [r.roomNumber, r.buildingName] as [string, string] + ); + // Handle 'ANY' building special case - return max 24 random rooms sorted by building if (building.toUpperCase() === 'ANY' && rooms.length > 0) { const shuffled = rooms @@ -40,16 +42,16 @@ export async function searchNow(building: string): Promise { .sort((a, b) => a.sort - b.sort) .map(({ value }) => value) .slice(0, Math.min(24, rooms.length)); - + // Sort by building name, then room number shuffled.sort((a, b) => { if (a[1] !== b[1]) return a[1].localeCompare(b[1]); return a[0].localeCompare(b[0]); }); - + return { Rooms: shuffled }; } - + return { Rooms: rooms }; } @@ -60,22 +62,24 @@ export async function searchNow(building: string): Promise { export async function searchAt( building: string, time: string, - days: string[] = [] + days: Array = [] ): Promise { // Python capitalizes the days: [x.capitalize() for x in d] const capitalizedDays = days.map(d => d.charAt(0).toUpperCase() + d.slice(1).toLowerCase()); - + const params: RoomSearchParams = { building: building.toUpperCase(), timeType: 'at', timeA: time, - days: capitalizedDays + days: capitalizedDays, }; const result = await lookup(params); // Type guard: 'at' returns RoomAvailabilityResult[] - const rooms = (result as RoomAvailabilityResult[]).map(r => [r.roomNumber, r.buildingName] as [string, string]); - + const rooms = (result as Array).map( + r => [r.roomNumber, r.buildingName] as [string, string] + ); + return { Rooms: rooms }; } @@ -87,23 +91,25 @@ export async function searchBetween( building: string, timeA: string, timeB: string, - days: string[] = [] + days: Array = [] ): Promise { // Python capitalizes the days: [x.capitalize() for x in d] const capitalizedDays = days.map(d => d.charAt(0).toUpperCase() + d.slice(1).toLowerCase()); - + const params: RoomSearchParams = { building: building.toUpperCase(), timeType: 'between', timeA, timeB, - days: capitalizedDays + days: capitalizedDays, }; const result = await lookup(params); // Type guard: 'between' returns RoomAvailabilityResult[] - const rooms = (result as RoomAvailabilityResult[]).map(r => [r.roomNumber, r.buildingName] as [string, string]); - + const rooms = (result as Array).map( + r => [r.roomNumber, r.buildingName] as [string, string] + ); + return { Rooms: rooms }; } @@ -116,55 +122,59 @@ export async function searchWhen(building: string, room: string): Promise; + // No events found if (events.length === 0) { return { busySince: '', busyUntil: '', - isInUse: false + isInUse: false, }; } // Get current Mountain time to check if room is in use const now = new Date(); const mountainTime = new Date(now.toLocaleString('en-US', { timeZone: 'America/Denver' })); - + // Get today's date for building datetime objects const today = mountainTime; const dayEvents = events.map(e => ({ name: e.name, start: parseTimeToDate(today, e.startTime), - end: parseTimeToDate(today, e.endTime) + end: parseTimeToDate(today, e.endTime), })); // Python logic: Find first event and determine busy_until based on gap between events - let busySince = dayEvents[0]!.start; - let busyUntil = dayEvents[0]!.end; + const busySince = dayEvents[0]?.start; + let busyUntil = dayEvents[0]?.end; if (dayEvents.length > 1) { // Check for gaps > 15 minutes between consecutive events for (let i = 0; i < dayEvents.length - 1; i++) { - const endTime = dayEvents[i]!.end; - const nextStartTime = dayEvents[i + 1]!.start; - const gapMinutes = (nextStartTime.getTime() - endTime.getTime()) / (1000 * 60); - + const endTime = dayEvents[i]?.end; + const nextStartTime = dayEvents[i + 1]?.start; + const gapMinutes = + !!nextStartTime && !!endTime + ? (nextStartTime.getTime() - endTime.getTime()) / (1000 * 60) + : 0; + if (gapMinutes > 15) { busyUntil = endTime; break; } - busyUntil = dayEvents[i + 1]!.end; + busyUntil = dayEvents[i + 1]?.end; } } // Check if currently in use: busy_until > my_date > busy_since - const isInUse = mountainTime > busySince && mountainTime < busyUntil; + const isInUse = + !!busySince && !!busyUntil && mountainTime > busySince && mountainTime < busyUntil; // Format dates to match Python format: '2023-02-06T12:15:00-07:00' const formatDate = (date: Date): string => { @@ -178,9 +188,9 @@ export async function searchWhen(building: string, room: string): Promise { - const term = yearTerm || getCurrentYearTerm(); + const term = yearTerm ?? getCurrentYearTerm(); info(`[CRON] Starting scheduled room scrape for ${term}...`); try { - const result = await scrapeRoomData(term, (progress) => { + const result = await scrapeRoomData(term, progress => { if (progress.buildings % 5 === 0 && progress.buildings > 0) { - info(`[CRON] Progress: ${progress.buildings} buildings, ${progress.rooms} rooms, ${progress.events} events`); + info( + `[CRON] Progress: ${progress.buildings} buildings, ${progress.rooms} rooms, ${progress.events} events` + ); } }); - info(`[CRON] Scheduled scrape complete! Buildings: ${result.buildings}, Rooms: ${result.rooms}, Events: ${result.events}`); - } catch (err) { + info( + `[CRON] Scheduled scrape complete! Buildings: ${result.buildings}, Rooms: ${result.rooms}, Events: ${result.events}` + ); + } catch (error_) { error('[CRON] Scheduled scrape failed:'); - error(err); + error(error_); } }, null, // onComplete @@ -58,16 +59,16 @@ export function setupScraperCron( /** * Example usage - add to your main bot startup file (index.ts or similar): - * + * * ```typescript * import { setupScraperCron } from './roomFinder/cron.js'; - * + * * // Run scraper every Sunday at 2 AM Mountain Time * setupScraperCron(); - * + * * // Or with custom schedule (every day at 3 AM): * setupScraperCron('0 3 * * *'); - * + * * // Or with specific year_term: * setupScraperCron('0 2 * * 0', '20251'); * ``` diff --git a/src/roomFinder/index.ts b/src/roomFinder/index.ts index ee1a08dd..79d5268a 100644 --- a/src/roomFinder/index.ts +++ b/src/roomFinder/index.ts @@ -1,12 +1,3 @@ -// Room Finder Module -// Ported from Python implementation to TypeScript with Prisma - export * from './types.js'; export * from './search.js'; export * from './utils.js'; - -// Main lookup function for finding available rooms -export { lookup } from './search.js'; - -// Utility functions -export * from './utils.js'; diff --git a/src/roomFinder/scraper.test.ts b/src/roomFinder/scraper.test.ts index 7bd36388..47009ae7 100644 --- a/src/roomFinder/scraper.test.ts +++ b/src/roomFinder/scraper.test.ts @@ -98,23 +98,23 @@ describe('roomFinder scraper utilities', () => { describe('getScraperStatus', () => { test('returns status object with correct structure', () => { const status = getScraperStatus(); - + expect(status).toHaveProperty('isRunning'); expect(status).toHaveProperty('yearTerm'); expect(status).toHaveProperty('startTime'); expect(status).toHaveProperty('recentLogs'); - + expect(typeof status.isRunning).toBe('boolean'); expect(Array.isArray(status.recentLogs)).toBe(true); }); test('returns correct types', () => { const status = getScraperStatus(); - + if (status.yearTerm !== null) { expect(typeof status.yearTerm).toBe('string'); } - + if (status.startTime !== null) { expect(status.startTime).toBeInstanceOf(Date); } diff --git a/src/roomFinder/scraper.ts b/src/roomFinder/scraper.ts index 7f38b58b..0d8f89f4 100644 --- a/src/roomFinder/scraper.ts +++ b/src/roomFinder/scraper.ts @@ -1,21 +1,21 @@ /** * BYU Room Finder Scraper - * + * * Scrapes class schedule data from BYU's class schedule system and populates the database. * Port of the Python scraper from Roomfinder-SQLite. - * + * * USAGE: * - Via Discord: /scraperooms * - Via code: scrapeRoomData('20251') * - Via cron: Schedule scrapeRoomData() to run automatically - * + * * YEAR_TERM format: YYYYT where T is term (1=Winter, 3=Spring, 4=Summer, 5=Fall) */ import axios from 'axios'; import * as cheerio from 'cheerio'; -import * as fs from 'fs/promises'; -import * as path from 'path'; +import fs from 'node:fs/promises'; +import path from 'node:path'; import { db } from '../database/index.js'; import { error as logError, info as logInfo } from '../logger.js'; @@ -29,7 +29,7 @@ const COLUMNS = { let isScraperRunning = false; let currentScrapeYearTerm: string | null = null; let scrapeStartTime: Date | null = null; -const recentLogs: string[] = []; +const recentLogs: Array = []; const MAX_LOG_BUFFER = 10; const TIME_FORMAT = /(\d{1,2}):(\d{2})(am|pm)/i; @@ -37,19 +37,19 @@ const TIME_FORMAT = /(\d{1,2}):(\d{2})(am|pm)/i; // Retry configuration const MAX_RETRIES = 3; const INITIAL_RETRY_DELAY = 1000; // 1 second -const REQUEST_TIMEOUT = 30000; // 30 seconds (BYU's server can be slow) +const REQUEST_TIMEOUT = 30_000; // 30 seconds (BYU's server can be slow) interface ClassInfo { name: string; start: { hours: number; minutes: number; seconds: number }; end: { hours: number; minutes: number; seconds: number }; - days: string[]; + days: Array; } interface RoomInfo { description: string; capacity: number; - classes: ClassInfo[]; + classes: Array; } interface ScraperProgress { @@ -85,7 +85,7 @@ export function getScraperStatus(): { isRunning: boolean; yearTerm: string | null; startTime: Date | null; - recentLogs: string[]; + recentLogs: Array; } { return { isRunning: isScraperRunning, @@ -137,34 +137,41 @@ async function retryWithBackoff( for (let attempt = 0; attempt <= retries; attempt++) { try { return await fn(); - } catch (err) { - lastError = err as Error; + } catch (error) { + lastError = error as Error; // Check if it's a network error that's worth retrying const isRetryable = - err && typeof err === 'object' && - ('code' in err && ( - err.code === 'ECONNRESET' || - err.code === 'ETIMEDOUT' || - err.code === 'ECONNREFUSED' || - err.code === 'ENOTFOUND' - )); + error && + typeof error === 'object' && + 'code' in error && + (error.code === 'ECONNRESET' || + error.code === 'ETIMEDOUT' || + error.code === 'ECONNREFUSED' || + error.code === 'ENOTFOUND'); if (!isRetryable || attempt === retries) { // Not retryable or out of retries - throw err; + throw error; } // Calculate backoff delay: 1s, 2s, 4s const delay = INITIAL_RETRY_DELAY * Math.pow(2, attempt); - logInfo(`${context} failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${delay}ms...`); + logInfo( + `${context} failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${delay}ms...` + ); addLog(`Retry ${attempt + 1}/${retries + 1} for ${context} after ${delay}ms`); await sleep(delay); } } - throw lastError; + if (lastError) { + throw lastError; + } + + // This should never be reached, but TypeScript needs a guaranteed return/throw + throw new Error(`${context} failed: No error recorded`); } /** @@ -179,22 +186,19 @@ async function openOrDownloadFile( const cachePath = path.join(cacheDir, filename); try { - const html = await fs.readFile(cachePath, 'utf-8'); + const html = await fs.readFile(cachePath, 'utf8'); return html; - } catch (err) { + } catch { // File doesn't exist, download it with retry logic logInfo(`Downloading ${filename}...`); - const html = await retryWithBackoff( - fetchFn, - `Download ${filename}` - ); + const html = await retryWithBackoff(fetchFn, `Download ${filename}`); // Create cache directory await fs.mkdir(cacheDir, { recursive: true }); // Save to cache - await fs.writeFile(cachePath, html, 'utf-8'); + await fs.writeFile(cachePath, html, 'utf8'); // Sleep to avoid overwhelming the server await sleep(150); @@ -207,15 +211,15 @@ async function openOrDownloadFile( * Parses time string like "10:00am" or "2:30pm" to hours/minutes */ function parseTime(timeStr: string): { hours: number; minutes: number; seconds: number } { - const match = timeStr.match(TIME_FORMAT); + const match = TIME_FORMAT.exec(timeStr); if (!match) { logError(`Failed to parse time: ${timeStr}`); return { hours: 1, minutes: 0, seconds: 0 }; } - let hours = parseInt(match[1]!, 10); - const minutes = parseInt(match[2]!, 10); - const period = match[3]!.toLowerCase(); + let hours = Number.parseInt(match[1] ?? '0', 10); + const minutes = Number.parseInt(match[2] ?? '0', 10); + const period = match[3]?.toLowerCase(); // Convert to 24-hour format if (period === 'pm' && hours !== 12) { @@ -250,8 +254,8 @@ function getClassInfo($: cheerio.Root, row: cheerio.Element): ClassInfo { const timePeriod = $(cells[COLUMNS.class_period]) .text() .trim() - .replace(/a/gi, 'am') // Replace ALL 'a' with 'am' (global) - .replace(/p/gi, 'pm'); // Replace ALL 'p' with 'pm' (global) + .replaceAll(/a/gi, 'am') // Replace ALL 'a' with 'am' (global) + .replaceAll(/p/gi, 'pm'); // Replace ALL 'p' with 'pm' (global) const [startStr, endStr] = timePeriod.split(' - '); @@ -261,8 +265,8 @@ function getClassInfo($: cheerio.Root, row: cheerio.Element): ClassInfo { start = parseTime(startStr); end = parseTime(endStr); - } catch (err) { - logError(`Error parsing time: ${err}`); + } catch (error) { + logError(`Error parsing time: ${error}`); start = { hours: 1, minutes: 0, seconds: 0 }; end = { hours: 1, minutes: 0, seconds: 0 }; } @@ -270,13 +274,13 @@ function getClassInfo($: cheerio.Root, row: cheerio.Element): ClassInfo { const daysText = $(cells[COLUMNS.days]).text().trim(); // Parse days - handle "Daily" or specific days like "MWF" or "T Th" - let days: string[]; + let days: Array; if (daysText === 'Daily') { days = ['M', 'T', 'W', 'Th', 'F']; } else { // Match: Th, Sa, Su, M, T, W, F (in that order to match Th before T) const matches = daysText.match(/Th|Sa|Su|M|T|W|F/g); - days = matches || []; + days = matches ?? []; } return { @@ -291,30 +295,26 @@ function getClassInfo($: cheerio.Root, row: cheerio.Element): ClassInfo { * Fetches and parses room information */ async function getRoomInfo(yearTerm: string, building: string, room: string): Promise { - const html = await openOrDownloadFile( - yearTerm, - `${building}-${room}.html`, - async () => { - const response = await axios.post( - 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', - new URLSearchParams({ - year_term: yearTerm, - building: building, - room: room, - }), - { timeout: REQUEST_TIMEOUT } - ); - return response.data; - } - ); + const html = await openOrDownloadFile(yearTerm, `${building}-${room}.html`, async () => { + const response = await axios.post( + 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', + new URLSearchParams({ + year_term: yearTerm, + building: building, + room: room, + }), + { timeout: REQUEST_TIMEOUT } + ); + return response.data as string; + }); const $ = cheerio.load(html); - const description = $('input[name="room_desc"]').val() as string || 'UNKNOWN'; - const capacityStr = $('input[name="capacity"]').val() as string || '0'; - const capacity = parseInt(capacityStr, 10) || 0; + const description = $('input[name="room_desc"]').val() || 'UNKNOWN'; + const capacityStr = $('input[name="capacity"]').val() || '0'; + const capacity = Number.parseInt(capacityStr, 10) || 0; - const classes: ClassInfo[] = []; + const classes: Array = []; // Find the schedule table (contains "Instructor" header) const scheduleHeader = $('th:contains("Instructor")'); @@ -339,25 +339,21 @@ async function getRoomInfo(yearTerm: string, building: string, room: string): Pr */ async function* getBuildingsRooms( yearTerm: string, - buildings: string[] -): AsyncGenerator<[string, string[]]> { + buildings: Array +): AsyncGenerator<[string, Array]> { for (const building of buildings) { - const html = await openOrDownloadFile( - yearTerm, - `${building}-list.html`, - async () => { - const response = await axios.post( - 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', - new URLSearchParams({ - e: '@loadRooms', - year_term: yearTerm, - building: building, - }), - { timeout: REQUEST_TIMEOUT } - ); - return response.data; - } - ); + const html = await openOrDownloadFile(yearTerm, `${building}-list.html`, async () => { + const response = await axios.post( + 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', + new URLSearchParams({ + e: '@loadRooms', + year_term: yearTerm, + building: building, + }), + { timeout: REQUEST_TIMEOUT } + ); + return response.data as string; + }); const $ = cheerio.load(html); const rooms = $('table a') @@ -375,7 +371,7 @@ export async function scrapeRoomData( yearTerm?: string, onProgress?: (progress: ScraperProgress) => void ): Promise { - const term = yearTerm || getCurrentYearTerm(); + const term = yearTerm ?? getCurrentYearTerm(); // Check if scraper is already running if (isScraperRunning) { @@ -412,24 +408,20 @@ export async function scrapeRoomData( // Fetch building list from main page logInfo('Fetching building list...'); addLog('Fetching building list from BYU...'); - const indexHtml = await openOrDownloadFile( - term, - 'classRoom2.cgi', - async () => { - const response = await axios.post( - 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', - new URLSearchParams({ year_term: term }), - { timeout: REQUEST_TIMEOUT } - ); - return response.data; - } - ); + const indexHtml = await openOrDownloadFile(term, 'classRoom2.cgi', async () => { + const response = await axios.post( + 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', + new URLSearchParams({ year_term: term }), + { timeout: REQUEST_TIMEOUT } + ); + return response.data as string; + }); const $ = cheerio.load(indexHtml); const buildings = $('select[name="Building"] option') - .map((_: number, el: cheerio.Element) => $(el).val() as string) + .map((_: number, el: cheerio.Element) => $(el).val()) .get() - .filter((val: string) => val && val.trim()); // Remove empty values + .filter((val: string) => (val ? val.trim() : '')) as Array; // Remove empty values logInfo(`Found ${buildings.length} buildings`); addLog(`Found ${buildings.length} buildings to process`); @@ -483,16 +475,19 @@ export async function scrapeRoomData( } } - logInfo(`Scrape complete! Buildings: ${progress.buildings}, Rooms: ${progress.rooms}, Events: ${progress.events}`); - addLog(`✅ Complete! ${progress.buildings} buildings, ${progress.rooms} rooms, ${progress.events} events`); + logInfo( + `Scrape complete! Buildings: ${progress.buildings}, Rooms: ${progress.rooms}, Events: ${progress.events}` + ); + addLog( + `✅ Complete! ${progress.buildings} buildings, ${progress.rooms} rooms, ${progress.events} events` + ); return progress; - - } catch (err) { + } catch (error) { logError('Scraper error:'); - logError(err); - addLog(`❌ Error: ${err instanceof Error ? err.message : 'Unknown error'}`); - throw err; + logError(error); + addLog(`❌ Error: ${error instanceof Error ? error.message : 'Unknown error'}`); + throw error; } finally { // Always clear running status when done isScraperRunning = false; diff --git a/src/roomFinder/search.test.ts b/src/roomFinder/search.test.ts index 5fbb5c54..11893e03 100644 --- a/src/roomFinder/search.test.ts +++ b/src/roomFinder/search.test.ts @@ -15,7 +15,7 @@ describe('roomFinder search', () => { beforeEach(() => { vi.clearAllMocks(); - + // Mock current time to be consistent vi.useFakeTimers(); vi.setSystemTime(new Date('2025-01-20T14:00:00.000Z')); // Monday 2pm UTC @@ -48,9 +48,7 @@ describe('roomFinder search', () => { days: [], }); - expect(result).toEqual([ - { roomNumber: '350', buildingName: 'TMCB' }, - ]); + expect(result).toEqual([{ roomNumber: '350', buildingName: 'TMCB' }]); }); test('excludes rooms with conflicting events', async () => { @@ -151,9 +149,7 @@ describe('roomFinder search', () => { days: ['M', 'W'], }); - expect(result).toEqual([ - { roomNumber: '350', buildingName: 'TMCB' }, - ]); + expect(result).toEqual([{ roomNumber: '350', buildingName: 'TMCB' }]); }); test('returns empty array when no days specified', async () => { @@ -219,9 +215,7 @@ describe('roomFinder search', () => { days: ['M', 'W', 'F'], }); - expect(result).toEqual([ - { roomNumber: '350', buildingName: 'TMCB' }, - ]); + expect(result).toEqual([{ roomNumber: '350', buildingName: 'TMCB' }]); }); test('returns empty array when no days specified', async () => { diff --git a/src/roomFinder/search.ts b/src/roomFinder/search.ts index 09e74afe..1d3d4992 100644 --- a/src/roomFinder/search.ts +++ b/src/roomFinder/search.ts @@ -1,15 +1,21 @@ import { db } from '../database/index.js'; -import { type RoomSearchParams, type RoomAvailabilityResult, type EventInfo, DAY_MAP } from './types.js'; -import { Prisma } from '@prisma/client'; +import { + type RoomSearchParams, + type RoomAvailabilityResult, + type EventInfo, + DAY_MAP, +} from './types.js'; -export async function lookup(params: RoomSearchParams): Promise { +export async function lookup( + params: RoomSearchParams +): Promise | Array> { const { building, room, timeType, timeA, timeB, days } = params; // Fetch building record if building is specified let buildingRecord = null; if (building && building !== 'ANY') { buildingRecord = await db.buildings.findFirst({ - where: { name: building } + where: { name: building }, }); if (!buildingRecord) { @@ -18,27 +24,27 @@ export async function lookup(params: RoomSearchParams): Promise ({ - days: { contains: `"${day}"` } - })) + days: { + contains: `"${currentDay}"`, + }, }, { - startTime: { lte: timeA } + startTime: { lte: currentTime }, }, { - endTime: { gt: timeA } - } - ] + endTime: { gt: currentTime }, + }, + ], }, include: { - room: true - } + room: true, + }, }); + + break; } - } else if (timeType === 'between') { - if (!timeA || !timeB || !isValidTimeFormat(timeA) || !isValidTimeFormat(timeB)) { - throw new Error('Valid timeA and timeB are required for "between" queries'); - } + case 'when': { + // Get current events for specific room + if (!building || !room) { + throw new Error('Building and room are required for "when" queries'); + } - // Check for conflicting events in time range - // Python uses: .where(SQL("EXISTS (SELECT 1 FROM json_each(days) WHERE value IN (...))", days)) - // For SQLite JSON arrays, we need to check if any of the input days are in the JSON array - if (days.length === 0) { - // No days specified, return empty - conflictingEventsQuery = db.events.findMany({ - where: { id: -1 }, // No results - include: { room: true } - }); - } else { - conflictingEventsQuery = db.events.findMany({ + currentEventsQuery = db.events.findMany({ where: { AND: [ { - OR: days.map(day => ({ - days: { contains: `"${day}"` } - })) + room: { + building: { + name: building, + }, + number: room, + }, }, { - OR: [ - { - AND: [ - { startTime: { lte: timeA } }, - { endTime: { gt: timeA } } - ] - }, - { - AND: [ - { startTime: { lt: timeB } }, - { endTime: { gte: timeB } } - ] - }, - { - AND: [ - { startTime: { gte: timeA } }, - { endTime: { lte: timeB } } - ] - } - ] - } - ] + endTime: { + gte: currentTime, + }, + }, + { + days: { + contains: `"${currentDay}"`, + }, + }, + ], }, include: { - room: true - } + room: { + include: { + building: true, + }, + }, + }, + orderBy: { + startTime: 'asc', + }, + take: 5, }); + + break; + } + case 'at': { + if (!timeA || !isValidTimeFormat(timeA)) { + throw new Error('Valid timeA is required for "at" queries'); + } + + // Check for conflicting events at specific time + // Python uses: .where(SQL("EXISTS (SELECT 1 FROM json_each(days) WHERE value IN (...))", days)) + // For SQLite JSON arrays, we need to check if any of the input days are in the JSON array + if (days.length === 0) { + // No days specified, return empty + conflictingEventsQuery = db.events.findMany({ + where: { id: -1 }, // No results + include: { room: true }, + }); + } else { + conflictingEventsQuery = db.events.findMany({ + where: { + AND: [ + { + OR: days.map(day => ({ + days: { contains: `"${day}"` }, + })), + }, + { + startTime: { lte: timeA }, + }, + { + endTime: { gt: timeA }, + }, + ], + }, + include: { + room: true, + }, + }); + } + + break; + } + case 'between': { + if (!timeA || !timeB || !isValidTimeFormat(timeA) || !isValidTimeFormat(timeB)) { + throw new Error('Valid timeA and timeB are required for "between" queries'); + } + + // Check for conflicting events in time range + // Python uses: .where(SQL("EXISTS (SELECT 1 FROM json_each(days) WHERE value IN (...))", days)) + // For SQLite JSON arrays, we need to check if any of the input days are in the JSON array + if (days.length === 0) { + // No days specified, return empty + conflictingEventsQuery = db.events.findMany({ + where: { id: -1 }, // No results + include: { room: true }, + }); + } else { + conflictingEventsQuery = db.events.findMany({ + where: { + AND: [ + { + OR: days.map(day => ({ + days: { contains: `"${day}"` }, + })), + }, + { + OR: [ + { + AND: [{ startTime: { lte: timeA } }, { endTime: { gt: timeA } }], + }, + { + AND: [{ startTime: { lt: timeB } }, { endTime: { gte: timeB } }], + }, + { + AND: [{ startTime: { gte: timeA } }, { endTime: { lte: timeB } }], + }, + ], + }, + ], + }, + include: { + room: true, + }, + }); + } + + break; } + // No default } if (timeType === 'when') { // Return current events for the room const currentEvents = await currentEventsQuery; - return currentEvents.map((event: { name: any; startTime: any; endTime: any; }) => ({ + return currentEvents.map((event: { name: string; startTime: string; endTime: string }) => ({ name: event.name, startTime: event.startTime, - endTime: event.endTime + endTime: event.endTime, })); } else { // Get conflicting room IDs const conflictingEvents = await conflictingEventsQuery; - const conflictingRoomIds = conflictingEvents.map((event: { roomId: any; }) => event.roomId); + const conflictingRoomIds = conflictingEvents.map((event: { roomId: number }) => event.roomId); // Get available rooms (not in conflicting events) const availableRooms = await db.rooms.findMany({ where: { id: { - notIn: conflictingRoomIds.length > 0 ? conflictingRoomIds : [-1] + notIn: conflictingRoomIds.length > 0 ? conflictingRoomIds : [-1], }, description: 'CLASSROOM', - ...(building && building !== 'ANY' && buildingRecord ? { buildingId: buildingRecord.id } : {}) + ...(building && building !== 'ANY' && buildingRecord + ? { buildingId: buildingRecord.id } + : {}), }, include: { - building: true + building: true, }, - orderBy: [ - { building: { name: 'asc' } }, - { number: 'asc' } - ] + orderBy: [{ building: { name: 'asc' } }, { number: 'asc' }], }); - return availableRooms.map((room: { number: any; building: { name: any; }; }) => ({ - roomNumber: room.number, - buildingName: room.building.name + return availableRooms.map((r: { number: string; building: { name: string } }) => ({ + roomNumber: r.number, + buildingName: r.building.name, })); } } diff --git a/src/roomFinder/types.ts b/src/roomFinder/types.ts index 45fafdba..2999901b 100644 --- a/src/roomFinder/types.ts +++ b/src/roomFinder/types.ts @@ -1,62 +1,62 @@ export interface Building { - id: number; - name: string; + id: number; + name: string; } export interface Room { - id: number; - buildingId: number; - building: Building; - number: string; - description: string; + id: number; + buildingId: number; + building: Building; + number: string; + description: string; } export interface Event { - id: number; - roomId: number; - room: Room; - name: string; - days: string; // JSON array of days: ["M", "T", "W", "Th", "F", "Sa", "Su"] - startTime: string; // TIME format: "HH:MM:SS" - endTime: string; // TIME format: "HH:MM:SS" + id: number; + roomId: number; + room: Room; + name: string; + days: string; // JSON array of days: ["M", "T", "W", "Th", "F", "Sa", "Su"] + startTime: string; // TIME format: "HH:MM:SS" + endTime: string; // TIME format: "HH:MM:SS" } export interface RoomSearchParams { - building?: string; - room?: string; - timeType: 'now' | 'when' | 'at' | 'between'; - timeA?: string; // For 'at' and 'between' types - timeB?: string; // For 'between' type - days: string[]; // Array of day abbreviations + building?: string; + room?: string; + timeType: 'now' | 'when' | 'at' | 'between'; + timeA?: string; // For 'at' and 'between' types + timeB?: string; // For 'between' type + days: Array; // Array of day abbreviations } export interface RoomAvailabilityResult { - roomNumber: string; - buildingName: string; + roomNumber: string; + buildingName: string; } export interface EventInfo { - name: string; - startTime: string; - endTime: string; + name: string; + startTime: string; + endTime: string; } export const DAY_MAP: Record = { - 'Mon': 'M', - 'Tue': 'T', - 'Wed': 'W', - 'Thu': 'Th', - 'Fri': 'F', - 'Sat': 'Sa', - 'Sun': 'Su', + Mon: 'M', + Tue: 'T', + Wed: 'W', + Thu: 'Th', + Fri: 'F', + Sat: 'Sa', + Sun: 'Su', }; export const REVERSE_DAY_MAP: Record = { - 'M': 'Mon', - 'T': 'Tue', - 'W': 'Wed', - 'Th': 'Thu', - 'F': 'Fri', - 'Sa': 'Sat', - 'Su': 'Sun', + M: 'Mon', + T: 'Tue', + W: 'Wed', + Th: 'Thu', + F: 'Fri', + Sa: 'Sat', + Su: 'Sun', }; diff --git a/src/roomFinder/utils.ts b/src/roomFinder/utils.ts index dbbe9a3d..6dfc9e7c 100644 --- a/src/roomFinder/utils.ts +++ b/src/roomFinder/utils.ts @@ -1,65 +1,65 @@ import { db } from '../database/index.js'; -import type { Building, Room, Event, RoomSearchParams, RoomAvailabilityResult, EventInfo } from './types.js'; +import type { Building, Room, Event, RoomAvailabilityResult } from './types.js'; /** * Get all available buildings */ -export async function getAllBuildings(): Promise { +export async function getAllBuildings(): Promise> { return await db.buildings.findMany({ orderBy: { - name: 'asc' - } + name: 'asc', + }, }); } /** * Get all rooms for a specific building */ -export async function getRoomsByBuilding(buildingName: string): Promise { +export async function getRoomsByBuilding(buildingName: string): Promise> { return await db.rooms.findMany({ where: { building: { - name: buildingName - } + name: buildingName, + }, }, include: { - building: true + building: true, }, orderBy: { - number: 'asc' - } + number: 'asc', + }, }); } /** * Get all rooms */ -export async function getAllRooms(): Promise { +export async function getAllRooms(): Promise> { return await db.rooms.findMany({ include: { - building: true + building: true, }, - orderBy: [ - { building: { name: 'asc' } }, - { number: 'asc' } - ] + orderBy: [{ building: { name: 'asc' } }, { number: 'asc' }], }); } /** * Get events for a specific room on a specific day */ -export async function getRoomEvents(roomId: number, day: string): Promise[]> { +export async function getRoomEvents( + roomId: number, + day: string +): Promise>> { return await db.events.findMany({ where: { roomId: roomId, days: { - contains: `"${day}"` - } + contains: `"${day}"`, + }, }, orderBy: { - startTime: 'asc' - } + startTime: 'asc', + }, }); } @@ -81,9 +81,13 @@ export async function isRoomAvailable(roomId: number, time: string, day: string) /** * Get available rooms at a specific time */ -export async function getAvailableRooms(time: string, day: string, buildingName?: string): Promise { +export async function getAvailableRooms( + time: string, + day: string, + buildingName?: string +): Promise> { const allRooms = await getAllRooms(); - const availableRooms: RoomAvailabilityResult[] = []; + const availableRooms: Array = []; for (const room of allRooms) { // Skip if building filter is specified and doesn't match @@ -95,7 +99,7 @@ export async function getAvailableRooms(time: string, day: string, buildingName? if (isAvailable) { availableRooms.push({ roomNumber: room.number, - buildingName: room.building.name + buildingName: room.building.name, }); } } @@ -111,9 +115,9 @@ export async function getAvailableRooms(time: string, day: string, buildingName? /** * Parse days array from JSON string */ -export function parseDays(daysJson: string): string[] { +export function parseDays(daysJson: string): Array { try { - const parsed = JSON.parse(daysJson); + const parsed = JSON.parse(daysJson) as Array; return Array.isArray(parsed) ? parsed : []; } catch { return []; @@ -123,7 +127,7 @@ export function parseDays(daysJson: string): string[] { /** * Format days array to JSON string */ -export function formatDays(days: string[]): string { +export function formatDays(days: Array): string { return JSON.stringify(days); } @@ -146,5 +150,5 @@ export function getCurrentTime(): string { */ export function getCurrentDay(): string { const days = ['Su', 'M', 'T', 'W', 'Th', 'F', 'Sa']; - return days[new Date().getDay()] || ''; + return days[new Date().getDay()] ?? ''; } From 068d2c95e90eb69975b5c3fd791323ff3b20a33d Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Fri, 24 Oct 2025 16:18:58 -0600 Subject: [PATCH 09/14] style: suppress eslint warnings for unbound method calls in tests --- src/roomFinder/utils.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/roomFinder/utils.test.ts b/src/roomFinder/utils.test.ts index 13a6f7c5..27534077 100644 --- a/src/roomFinder/utils.test.ts +++ b/src/roomFinder/utils.test.ts @@ -104,6 +104,7 @@ describe('roomFinder utils', () => { const result = await getAllBuildings(); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(dbMock.buildings.findMany).toHaveBeenCalledWith({ orderBy: { name: 'asc' }, }); @@ -126,6 +127,7 @@ describe('roomFinder utils', () => { const result = await getRoomsByBuilding('TMCB'); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(dbMock.rooms.findMany).toHaveBeenCalledWith({ where: { building: { name: 'TMCB' }, @@ -152,6 +154,7 @@ describe('roomFinder utils', () => { const result = await getAllRooms(); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(dbMock.rooms.findMany).toHaveBeenCalledWith({ include: { building: true }, orderBy: [{ building: { name: 'asc' } }, { number: 'asc' }], @@ -176,6 +179,7 @@ describe('roomFinder utils', () => { const result = await getRoomEvents(1, 'M'); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(dbMock.events.findMany).toHaveBeenCalledWith({ where: { roomId: 1, From bc733e91b1e7538f5df100fd81b468426034dc86 Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Fri, 24 Oct 2025 16:22:20 -0600 Subject: [PATCH 10/14] Attempting to fix Github action --- package-lock.json | 14 ++++++++++++++ package.json | 1 + 2 files changed, 15 insertions(+) diff --git a/package-lock.json b/package-lock.json index ac9b5828..c55f7b4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@types/tmp": "0.2.6", "@vitest/coverage-istanbul": "3.2.4", "eslint": "9.38.0", + "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-file-progress": "3.0.2", "eslint-plugin-import": "2.31.0", @@ -4649,6 +4650,19 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-import-context": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.8.tgz", diff --git a/package.json b/package.json index 34fd5080..a15bd07e 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@types/tmp": "0.2.6", "@vitest/coverage-istanbul": "3.2.4", "eslint": "9.38.0", + "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-file-progress": "3.0.2", "eslint-plugin-import": "2.31.0", From 9b053651bddb3bb74d58485c81de7c126cdd6675 Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Sun, 1 Feb 2026 15:46:02 -0700 Subject: [PATCH 11/14] Resolve Comments --- README.md | 4 +- package-lock.json | 92 ++++--------------- package.json | 1 - .../migration.sql | 20 +++- .../migration.sql | 2 - .../migration.sql | 16 ---- src/commands/findRoom.ts | 2 +- src/commands/stats.ts | 8 +- src/roomFinder/scraper.ts | 53 +++++------ 9 files changed, 69 insertions(+), 129 deletions(-) delete mode 100644 prisma/migrations/20251023171019_add_smitten_timestamp/migration.sql delete mode 100644 prisma/migrations/20251023174246_add_tag_model/migration.sql diff --git a/README.md b/README.md index 56616c90..074b29fe 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Searches for available classrooms on BYU Campus using real-time schedule data. B - **between** - Find rooms available during a time range on specific day(s) - **when** - Check when a specific room is next available -The room finder automatically updates its database every Sunday at 2 AM to stay current with the semester schedule. +The room finder automatically updates its database every Sunday at 2 AM to stay current with the semester schedule. Use [`/scraperooms`](#scraperooms) to update the database manually. ### /help @@ -90,7 +90,7 @@ Retrieves the profile picture of the given user. ### /scraperooms -**[Admin Only]** Manually triggers the room finder data scraper to update the database with current semester schedule information. This is useful for forcing an immediate update without waiting for the automatic Sunday schedule. Takes 10-15 minutes to complete and runs in the background. Shows real-time progress if a scrape is already running. +**[Admin Only by default]** Manually triggers the room finder data scraper to update the database with current semester schedule information. This is useful for forcing an immediate update without waiting for the automatic Sunday schedule. Takes 10-15 minutes to complete and runs in the background. Shows real-time progress if a scrape is already running. ### /sendtag diff --git a/package-lock.json b/package-lock.json index c55f7b4c..bb36a716 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@discordjs/voice": "0.18.0", "@prisma/client": "6.13.0", - "axios": "1.12.2", "cheerio": "1.1.2", "cron": "4.3.3", "dectalk-tts": "1.0.1", @@ -3029,12 +3028,6 @@ "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -3050,17 +3043,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3215,6 +3197,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3496,18 +3479,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -3784,15 +3755,6 @@ "node": ">= 14" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/discord-api-types": { "version": "0.37.119", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.119.tgz", @@ -3891,6 +3853,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4058,6 +4021,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4067,6 +4031,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -4082,6 +4047,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4094,6 +4060,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5366,6 +5333,7 @@ "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, "funding": [ { "type": "individual", @@ -5420,22 +5388,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -5455,6 +5407,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5517,6 +5470,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5541,6 +5495,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5651,6 +5606,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5712,6 +5668,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5724,6 +5681,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -5738,6 +5696,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -6610,6 +6569,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6637,27 +6597,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -7674,6 +7613,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, "license": "MIT" }, "node_modules/pstree.remy": { diff --git a/package.json b/package.json index a15bd07e..5a0f0608 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "dependencies": { "@discordjs/voice": "0.18.0", "@prisma/client": "6.13.0", - "axios": "1.12.2", "cheerio": "1.1.2", "cron": "4.3.3", "dectalk-tts": "1.0.1", diff --git a/prisma/migrations/20251023164854_add_user_model/migration.sql b/prisma/migrations/20251023164854_add_user_model/migration.sql index 20d89a43..414f550a 100644 --- a/prisma/migrations/20251023164854_add_user_model/migration.sql +++ b/prisma/migrations/20251023164854_add_user_model/migration.sql @@ -58,7 +58,19 @@ CREATE TABLE "User" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "userId" TEXT NOT NULL, "guildId" TEXT NOT NULL, - "smitten" BOOLEAN NOT NULL DEFAULT false + "smitten" BOOLEAN NOT NULL DEFAULT false, + "smittenAt" DATETIME +); + +-- CreateTable +CREATE TABLE "Tag" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "guildId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "content" TEXT NOT NULL, + "createdBy" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "useCount" INTEGER NOT NULL DEFAULT 0 ); -- CreateIndex @@ -69,3 +81,9 @@ CREATE UNIQUE INDEX "User_userId_key" ON "User"("userId"); -- CreateIndex CREATE UNIQUE INDEX "User_userId_guildId_key" ON "User"("userId", "guildId"); + +-- CreateIndex +CREATE INDEX "Tag_guildId_idx" ON "Tag"("guildId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Tag_guildId_name_key" ON "Tag"("guildId", "name"); diff --git a/prisma/migrations/20251023171019_add_smitten_timestamp/migration.sql b/prisma/migrations/20251023171019_add_smitten_timestamp/migration.sql deleted file mode 100644 index 1e022a64..00000000 --- a/prisma/migrations/20251023171019_add_smitten_timestamp/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "User" ADD COLUMN "smittenAt" DATETIME; diff --git a/prisma/migrations/20251023174246_add_tag_model/migration.sql b/prisma/migrations/20251023174246_add_tag_model/migration.sql deleted file mode 100644 index deec6a26..00000000 --- a/prisma/migrations/20251023174246_add_tag_model/migration.sql +++ /dev/null @@ -1,16 +0,0 @@ --- CreateTable -CREATE TABLE "Tag" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "guildId" TEXT NOT NULL, - "name" TEXT NOT NULL, - "content" TEXT NOT NULL, - "createdBy" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "useCount" INTEGER NOT NULL DEFAULT 0 -); - --- CreateIndex -CREATE INDEX "Tag_guildId_idx" ON "Tag"("guildId"); - --- CreateIndex -CREATE UNIQUE INDEX "Tag_guildId_name_key" ON "Tag"("guildId", "name"); diff --git a/src/commands/findRoom.ts b/src/commands/findRoom.ts index c86a084e..dc9f40d9 100644 --- a/src/commands/findRoom.ts +++ b/src/commands/findRoom.ts @@ -326,7 +326,7 @@ export const findRoom: GlobalCommand = { .setColor(embedColor) .setTimestamp() .setFooter({ - text: 'BYU Room Finder (Native)', + text: 'BYU Room Finder', iconURL: 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Brigham_Young_University_medallion.svg/240px-Brigham_Young_University_medallion.svg.png', }); diff --git a/src/commands/stats.ts b/src/commands/stats.ts index 68b69f5a..b765429d 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -1,10 +1,10 @@ -import type { ChatInputCommandInteraction } from 'discord.js'; import { + type ChatInputCommandInteraction, EmbedBuilder, SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandStringOption, - SlashCommandNumberOption, + type SlashCommandSubcommandBuilder, + type SlashCommandStringOption, + type SlashCommandNumberOption, userMention, } from 'discord.js'; diff --git a/src/roomFinder/scraper.ts b/src/roomFinder/scraper.ts index 0d8f89f4..a51f354f 100644 --- a/src/roomFinder/scraper.ts +++ b/src/roomFinder/scraper.ts @@ -12,7 +12,6 @@ * YEAR_TERM format: YYYYT where T is term (1=Winter, 3=Spring, 4=Summer, 5=Fall) */ -import axios from 'axios'; import * as cheerio from 'cheerio'; import fs from 'node:fs/promises'; import path from 'node:path'; @@ -142,13 +141,15 @@ async function retryWithBackoff( // Check if it's a network error that's worth retrying const isRetryable = - error && - typeof error === 'object' && - 'code' in error && - (error.code === 'ECONNRESET' || - error.code === 'ETIMEDOUT' || - error.code === 'ECONNREFUSED' || - error.code === 'ENOTFOUND'); + error instanceof TypeError || + (error instanceof DOMException && error.name === 'TimeoutError') || + (error && + typeof error === 'object' && + 'code' in error && + (error.code === 'ECONNRESET' || + error.code === 'ETIMEDOUT' || + error.code === 'ECONNREFUSED' || + error.code === 'ENOTFOUND')); if (!isRetryable || attempt === retries) { // Not retryable or out of retries @@ -296,16 +297,16 @@ function getClassInfo($: cheerio.Root, row: cheerio.Element): ClassInfo { */ async function getRoomInfo(yearTerm: string, building: string, room: string): Promise { const html = await openOrDownloadFile(yearTerm, `${building}-${room}.html`, async () => { - const response = await axios.post( - 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', - new URLSearchParams({ + const response = await fetch('https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', { + method: 'POST', + body: new URLSearchParams({ year_term: yearTerm, building: building, room: room, }), - { timeout: REQUEST_TIMEOUT } - ); - return response.data as string; + signal: AbortSignal.timeout(REQUEST_TIMEOUT), + }); + return await response.text(); }); const $ = cheerio.load(html); @@ -343,16 +344,16 @@ async function* getBuildingsRooms( ): AsyncGenerator<[string, Array]> { for (const building of buildings) { const html = await openOrDownloadFile(yearTerm, `${building}-list.html`, async () => { - const response = await axios.post( - 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', - new URLSearchParams({ + const response = await fetch('https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', { + method: 'POST', + body: new URLSearchParams({ e: '@loadRooms', year_term: yearTerm, building: building, }), - { timeout: REQUEST_TIMEOUT } - ); - return response.data as string; + signal: AbortSignal.timeout(REQUEST_TIMEOUT), + }); + return await response.text(); }); const $ = cheerio.load(html); @@ -409,12 +410,12 @@ export async function scrapeRoomData( logInfo('Fetching building list...'); addLog('Fetching building list from BYU...'); const indexHtml = await openOrDownloadFile(term, 'classRoom2.cgi', async () => { - const response = await axios.post( - 'https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', - new URLSearchParams({ year_term: term }), - { timeout: REQUEST_TIMEOUT } - ); - return response.data as string; + const response = await fetch('https://y.byu.edu/class_schedule/cgi/classRoom2.cgi', { + method: 'POST', + body: new URLSearchParams({ year_term: term }), + signal: AbortSignal.timeout(REQUEST_TIMEOUT), + }); + return await response.text(); }); const $ = cheerio.load(indexHtml); From 00ad1804a9222b6a605753ba7cf327ddd40d91c5 Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Sun, 1 Feb 2026 15:55:47 -0700 Subject: [PATCH 12/14] Merge origin/main into zyancey/CSBot/413/TSNativeRoomfinder --- .github/workflows/cd.yml | 60 + .github/workflows/ci.yml | 130 + .github/workflows/codeql.yml | 102 + .github/workflows/release.yml | 41 + eslint.config.ts | 105 + package-lock.json | 5261 +++++++++---------------- package.json | 48 +- src/@types/eslint-plugin-promise.d.ts | 15 + src/commands/stats.ts | 4 +- src/roomFinder/search.test.ts | 14 +- tsconfig.build.json | 10 + 11 files changed, 2320 insertions(+), 3470 deletions(-) create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/release.yml create mode 100644 eslint.config.ts create mode 100644 src/@types/eslint-plugin-promise.d.ts create mode 100644 tsconfig.build.json diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 00000000..121249eb --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,60 @@ +name: CD +description: Builds and publishes a Docker image to the GitHub container registry when a semver tag is pushed + +on: + push: + branches: [main] + # Publish semver tags as releases. + tags: ["v*.*.*"] + +permissions: {} + +env: + # TODO: Also publish to Codeberg + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + +jobs: + publish-image: + name: Publish image + + runs-on: ubuntu-latest + + permissions: + contents: read # Required for actions/checkout + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Log in to container registry + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + with: + # e.g. 'ghcr.io/byu-cs-discord/csbot' + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + + - name: Build and push image + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..f0904ecb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,130 @@ +name: CI +description: Lints, builds, tests, and checks versions of the project when a pull request is opened or merged + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +permissions: {} + +jobs: + lint: + name: Lint + + runs-on: ubuntu-latest + + permissions: + contents: read # Required for actions/checkout + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm clean-install + + - name: Generate Prisma client + run: npm run db:generate + + - name: Generate version + run: npm run export-version + + - name: Lint + run: npm run lint + + build: + name: Build + + runs-on: ubuntu-latest + + permissions: + contents: read # Required for actions/checkout + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm clean-install + + - name: Generate Prisma client + run: npm run db:generate + + - name: Generate version + run: npm run export-version + + - name: Build + run: npm run build + + test: + name: Test + + runs-on: ubuntu-latest + + permissions: + contents: read # Required for actions/checkout + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm clean-install + + - name: Generate Prisma client + run: npm run db:generate + + - name: Generate version + run: npm run export-version + + - name: Test + run: npm test + + # - name: Report coverage + # uses: ArtiomTr/jest-coverage-report-action@v2.2.2 + + check-versions: + name: Check versions + # Ensure that package.json and package-lock.json have the same version, and that CHANGELOG.md is spec-compliant + + runs-on: ubuntu-latest + + permissions: + contents: read # Required for actions/checkout + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm clean-install + + - name: Check versions + run: npm run release diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..50fe8ac1 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,102 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: CodeQL Advanced + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: 15 2 * * 6 + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: javascript-typescript + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # 4.31.10 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # 4.31.10 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..84dcc81b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release +description: On push to main, if the latest version in CHANGELOG.md is different from the latest version tag, create a new tag and release + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: {} + +jobs: + release: + name: Create release + + runs-on: ubuntu-latest + + # This sets appropriate permissions for the default `GITHUB_TOKEN`. + # Protected tags are incompatible with this arrangement for now. + permissions: + contents: write + discussions: write + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Parse changelog + id: changelog + uses: coditory/changelog-parser@4f567c6914ee75eff434b4d946393dfd254f8f98 # v1.0.2 + + - name: Create new tag and release + if: steps.changelog.outputs.status != 'unreleased' + # This action doesn't create a new release if the tag already exists + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 + with: + # Can't use GITHUB_TOKEN for tags, or our deploy workflow won't trigger. See https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow + token: ${{ secrets.RELEASE_TOKEN }} + tag_name: v${{ steps.changelog.outputs.version }} + name: v${{ steps.changelog.outputs.version }} + body: ${{ steps.changelog.outputs.description }} + prerelease: true # TODO: Change based on semver? diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 00000000..502fd2a3 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,105 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import js from '@eslint/js'; +import stylistic from '@stylistic/eslint-plugin'; +import { configs as typescriptConfigs } from 'typescript-eslint'; +import progress from 'eslint-plugin-file-progress'; +import importPlugin from 'eslint-plugin-import'; +import prettierRecommended from 'eslint-plugin-prettier/recommended'; +import promise from 'eslint-plugin-promise'; +import unicorn from 'eslint-plugin-unicorn'; + +export default defineConfig( + { files: ['**/*.{m,c,}{js,ts}{x,}'] }, + globalIgnores(['dist', 'coverage', 'src/constants/version.ts']), + { + linterOptions: { + reportUnusedDisableDirectives: 'error', + }, + }, + + js.configs.recommended, + { + rules: { + // Additions + curly: ['error', 'multi-line', 'consistent'], + 'max-nested-callbacks': ['error', { max: 4 }], + 'no-console': 'warn', + 'no-lonely-if': 'error', + yoda: 'error', + }, + }, + + typescriptConfigs.strictTypeChecked, + typescriptConfigs.stylisticTypeChecked, + { + languageOptions: { + parserOptions: { + projectService: true, + }, + }, + rules: { + // Overrides + '@typescript-eslint/array-type': ['error', { default: 'generic' }], + '@typescript-eslint/restrict-template-expressions': 'off', // FIXME Lots of errors + '@typescript-eslint/no-misused-spread': 'off', // Lots of things are spreadable in discord.js that eslint can't see well + + // Additions + '@typescript-eslint/explicit-function-return-type': 'error', + '@typescript-eslint/explicit-member-accessibility': 'error', + '@typescript-eslint/no-shadow': 'error', + }, + }, + + stylistic.configs['disable-legacy'], + stylistic.configs.customize({ + braceStyle: '1tbs', + indent: 'tab', + semi: true, + }), + { + rules: { + // Handled by Prettier + '@stylistic/arrow-parens': 'off', + '@stylistic/comma-dangle': 'off', + '@stylistic/indent': 'off', + '@stylistic/indent-binary-ops': 'off', + '@stylistic/no-mixed-spaces-and-tabs': 'off', + '@stylistic/quotes': 'off', + '@stylistic/operator-linebreak': 'off', + '@stylistic/quote-props': 'off', + }, + }, + + prettierRecommended, + + unicorn.configs.recommended, + { + rules: { + // Handled by Prettier + 'unicorn/no-nested-ternary': 'off', + + // Overrides + 'unicorn/filename-case': 'off', // We use camelCase + 'unicorn/no-array-callback-reference': 'off', + 'unicorn/no-null': 'off', // We use null + 'unicorn/prefer-spread': 'off', + 'unicorn/prefer-ternary': 'off', + 'unicorn/prevent-abbreviations': 'off', + }, + }, + + importPlugin.flatConfigs.recommended, + importPlugin.flatConfigs.typescript, + { + settings: { + 'import/resolver': { + typescript: true, + node: true, + }, + }, + }, + + progress.configs['recommended-ci'], + + promise.configs['flat/recommended'] +); diff --git a/package-lock.json b/package-lock.json index bb36a716..26e95254 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,46 +9,44 @@ "version": "0.15.0", "license": "0BSD", "dependencies": { - "@discordjs/voice": "0.18.0", + "@discordjs/voice": "0.19.0", "@prisma/client": "6.13.0", "cheerio": "1.1.2", "cron": "4.3.3", "dectalk-tts": "1.0.1", "discord.js": "14.19.1", - "ffmpeg-static": "5.2.0", - "libsodium-wrappers": "0.7.15", + "ffmpeg-static": "5.3.0", "source-map-support": "0.5.21", "superstruct": "2.0.2", "tmp": "0.2.5" }, "devDependencies": { - "@stylistic/eslint-plugin": "4.4.1", + "@stylistic/eslint-plugin": "5.7.0", "@types/cheerio": "0.22.35", - "@types/node": "20.17.48", - "@types/semver": "7.7.0", + "@types/node": "20.19.30", + "@types/semver": "7.7.1", "@types/source-map-support": "0.5.10", "@types/tmp": "0.2.6", - "@vitest/coverage-istanbul": "3.2.4", - "eslint": "9.38.0", - "eslint-config-prettier": "9.1.0", + "@vitest/coverage-istanbul": "4.0.17", + "eslint": "9.39.2", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-file-progress": "3.0.2", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-prettier": "5.5.3", + "eslint-plugin-import": "2.32.0", + "eslint-plugin-prettier": "5.5.5", "eslint-plugin-promise": "7.2.1", - "eslint-plugin-unicorn": "59.0.1", + "eslint-plugin-unicorn": "62.0.0", "genversion": "3.2.0", - "keep-a-changelog": "2.6.2", - "nodemon": "3.1.10", - "pm2": "6.0.8", - "prettier": "3.6.2", + "jiti": "2.6.1", + "keep-a-changelog": "2.8.0", + "nodemon": "3.1.11", + "prettier": "3.8.0", "prettier-plugin-prisma": "5.0.0", "prisma": "6.9.0", - "semver": "7.7.2", - "tsx": "4.20.6", - "typescript": "5.8.3", - "typescript-eslint": "8.29.1", - "vitest": "3.2.4", + "semver": "7.7.3", + "tsx": "4.21.0", + "typescript": "5.9.3", + "typescript-eslint": "8.53.1", + "vitest": "4.0.17", "vitest-mock-extended": "3.1.0" }, "engines": { @@ -302,19 +300,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -344,13 +344,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -407,14 +407,14 @@ } }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -499,12 +499,6 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@discordjs/builders/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", - "license": "MIT" - }, "node_modules/@discordjs/collection": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", @@ -529,16 +523,10 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@discordjs/formatters/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", - "license": "MIT" - }, "node_modules/@discordjs/rest": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.5.0.tgz", - "integrity": "sha512-PWhchxTzpn9EV3vvPRpwS0EE2rNYB9pvzDU/eLLW3mByJl0ZHZjHI2/wA8EbH2gRMQV7nu+0FoDF84oiPl8VAQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", + "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", "license": "Apache-2.0", "dependencies": { "@discordjs/collection": "^2.1.1", @@ -546,10 +534,10 @@ "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", - "discord-api-types": "^0.38.1", + "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", - "undici": "6.21.1" + "undici": "6.21.3" }, "engines": { "node": ">=18" @@ -570,11 +558,14 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@discordjs/rest/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", - "license": "MIT" + "node_modules/@discordjs/rest/node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } }, "node_modules/@discordjs/util": { "version": "1.1.1", @@ -589,32 +580,32 @@ } }, "node_modules/@discordjs/voice": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.18.0.tgz", - "integrity": "sha512-BvX6+VJE5/vhD9azV9vrZEt9hL1G+GlOdsQaVl5iv9n87fkXjf3cSwllhR3GdaUC8m6dqT8umXIWtn3yCu4afg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.19.0.tgz", + "integrity": "sha512-UyX6rGEXzVyPzb1yvjHtPfTlnLvB5jX/stAMdiytHhfoydX+98hfympdOwsnTktzr+IRvphxTbdErgYDJkEsvw==", "license": "Apache-2.0", "dependencies": { - "@types/ws": "^8.5.12", - "discord-api-types": "^0.37.103", + "@types/ws": "^8.18.1", + "discord-api-types": "^0.38.16", "prism-media": "^1.3.5", - "tslib": "^2.6.3", - "ws": "^8.18.0" + "tslib": "^2.8.1", + "ws": "^8.18.3" }, "engines": { - "node": ">=18" + "node": ">=22.12.0" }, "funding": { "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/ws": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.2.tgz", - "integrity": "sha512-dyfq7yn0wO0IYeYOs3z79I6/HumhmKISzFL0Z+007zQJMtAFGtt3AEoq1nuLXtcunUE5YYYQqgKvybXukAK8/w==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", "license": "Apache-2.0", "dependencies": { "@discordjs/collection": "^2.1.0", - "@discordjs/rest": "^2.5.0", + "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", @@ -642,12 +633,6 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@discordjs/ws/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", - "license": "MIT" - }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -682,690 +667,716 @@ "tslib": "^2.4.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ - "arm64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" + "aix" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ - "arm64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=18" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz", - "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.16.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.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" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint/js": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", - "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": ">=18" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.13.0", - "levn": "^0.4.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.18.0" + "node": ">=18" } }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.18.0" + "node": ">=18" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", - "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.9.0" + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "engines": { - "node": ">= 8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { - "node": ">= 8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">= 8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.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" + }, "engines": { - "node": ">=14" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/pkgr" + "url": "https://eslint.org/donate" } }, - "node_modules/@pm2/agent": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", - "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", - "dev": true, - "license": "AGPL-3.0", - "dependencies": { - "async": "~3.2.0", - "chalk": "~3.0.0", - "dayjs": "~1.8.24", - "debug": "~4.3.1", - "eventemitter2": "~5.0.1", - "fast-json-patch": "^3.1.0", - "fclone": "~1.0.11", - "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.0", - "proxy-agent": "~6.4.0", - "semver": "~7.5.0", - "ws": "~7.5.10" - } - }, - "node_modules/@pm2/agent/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@pm2/agent/node_modules/dayjs": { - "version": "1.8.36", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", - "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@pm2/agent/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "yallist": "^4.0.0" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@pm2/agent/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": ">=18.18.0" } }, - "node_modules/@pm2/agent/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@pm2/agent/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/@pm2/io": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", - "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, - "license": "Apache-2", - "dependencies": { - "async": "~2.6.1", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "require-in-the-middle": "^5.0.0", - "semver": "~7.5.4", - "shimmer": "^1.2.0", - "signal-exit": "^3.0.3", - "tslib": "1.9.3" - }, + "license": "Apache-2.0", "engines": { - "node": ">=6.0" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@pm2/io/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14" + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@pm2/io/node_modules/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, - "node_modules/@pm2/io/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/@pm2/io/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@pm2/io/node_modules/tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, - "license": "Apache-2.0" + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@pm2/io/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/@pm2/js-api": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", - "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, - "license": "Apache-2", + "license": "MIT", "dependencies": { - "async": "^2.6.3", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "extrareqp2": "^1.0.0", - "ws": "^7.0.0" - }, - "engines": { - "node": ">=4.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@pm2/js-api/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "lodash": "^4.17.14" + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.9.0" } }, - "node_modules/@pm2/js-api/node_modules/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@pm2/js-api/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@pm2/pm2-version-check": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", - "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", - "dev": true, - "dependencies": { - "debug": "^4.3.1" + "funding": { + "url": "https://opencollective.com/pkgr" } }, "node_modules/@prisma/client": { @@ -1400,6 +1411,16 @@ "jiti": "2.4.2" } }, + "node_modules/@prisma/config/node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/@prisma/debug": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.9.0.tgz", @@ -1775,18 +1796,26 @@ "npm": ">=7.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@stylistic/eslint-plugin": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz", - "integrity": "sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.7.0.tgz", + "integrity": "sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.32.1", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/types": "^8.52.0", + "eslint-visitor-keys": "^5.0.0", + "espree": "^11.0.0", "estraverse": "^5.3.0", - "picomatch": "^4.0.2" + "picomatch": "^4.0.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1795,142 +1824,41 @@ "eslint": ">=9.0.0" } }, - "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", - "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", - "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", - "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.34.1", - "@typescript-eslint/tsconfig-utils": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1", - "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": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", - "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/typescript-estree": "8.34.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", - "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.34.1", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", + "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/@stylistic/eslint-plugin/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@stylistic/eslint-plugin/node_modules/espree": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.0.tgz", + "integrity": "sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", "dependencies": { - "brace-expansion": "^2.0.1" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/eslint" } }, "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -1940,13 +1868,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -1959,13 +1880,14 @@ } }, "node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, "node_modules/@types/cheerio": { @@ -2012,18 +1934,18 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.48", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.48.tgz", - "integrity": "sha512-KpSfKOHPsiSC4IkZeu2LsusFwExAIVGkhG1KkbaBMLwau0uMhj0fCrvyg9ddM2sAvd+gtiBJLir4LAw1MNMIaw==", + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.21.0" } }, "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", "dev": true, "license": "MIT" }, @@ -2043,30 +1965,29 @@ "dev": true }, "node_modules/@types/ws": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", - "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", - "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", + "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/type-utils": "8.29.1", - "@typescript-eslint/utils": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/type-utils": "8.53.1", + "@typescript-eslint/utils": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2076,23 +1997,33 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "@typescript-eslint/parser": "^8.53.1", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", - "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", + "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2103,54 +2034,76 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", - "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.1", - "@typescript-eslint/types": "^8.34.1", - "debug": "^4.3.4" + "ms": "^2.1.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=6.0" }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", - "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", + "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.53.1", + "@typescript-eslint/types": "^8.53.1", + "debug": "^4.4.3" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", - "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", + "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1" + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2161,9 +2114,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", - "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", + "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", "dev": true, "license": "MIT", "engines": { @@ -2174,20 +2127,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", - "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", + "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/utils": "8.29.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/utils": "8.53.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2198,13 +2152,31 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@typescript-eslint/types": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", - "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", + "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", "dev": true, "license": "MIT", "engines": { @@ -2216,20 +2188,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", - "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", + "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", - "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": "^2.0.1" + "@typescript-eslint/project-service": "8.53.1", + "@typescript-eslint/tsconfig-utils": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2239,7 +2212,25 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { @@ -2259,16 +2250,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", - "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", + "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2279,18 +2270,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", - "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", + "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.53.1", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2301,9 +2292,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2583,82 +2574,65 @@ ] }, "node_modules/@vitest/coverage-istanbul": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-3.2.4.tgz", - "integrity": "sha512-IDlpuFJiWU9rhcKLkpzj8mFu/lpe64gVgnV15ZOrYx1iFzxxrxCzbExiUEKtwwXRvEiEMUS6iZeYgnMxgbqbxQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-4.0.17.tgz", + "integrity": "sha512-ayJXDFjASfKRwe4MlBxnC55busMQNxlWQu8i13q2V7/DT1KKUIfIqLgAphnBclqLmi/oAIC4JHcBF6GWZ3/EeQ==", "dev": true, "license": "MIT", "dependencies": { "@istanbuljs/schema": "^0.1.3", - "debug": "^4.4.1", + "@jridgewell/gen-mapping": "^0.3.13", + "@jridgewell/trace-mapping": "0.3.31", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-instrument": "^6.0.3", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magicast": "^0.3.5", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "3.2.4" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "vitest": "4.0.17" } }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz", + "integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==", "dev": true, "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@vitest/spy": "4.0.17", + "@vitest/utils": "4.0.17", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz", + "integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "4.0.17", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -2670,42 +2644,41 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", + "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz", + "integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.0.17", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz", + "integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "@vitest/pretty-format": "4.0.17", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { @@ -2713,28 +2686,24 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz", + "integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", + "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.0.17", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2806,41 +2775,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/amp": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", - "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==", - "dev": true, - "license": "MIT" - }, - "node_modules/amp-message": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", - "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "amp": "0.3.1" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2856,16 +2790,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansis": { - "version": "4.0.0-node10", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", - "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2886,13 +2810,14 @@ "dev": true }, "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { "node": ">= 0.4" @@ -2902,17 +2827,20 @@ } }, "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "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" + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2922,17 +2850,19 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2942,15 +2872,16 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -2960,15 +2891,16 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -2978,19 +2910,19 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -3009,30 +2941,28 @@ "node": ">=12" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -3049,14 +2979,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "node_modules/baseline-browser-mapping": { + "version": "2.9.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.16.tgz", + "integrity": "sha512-KeUZdBuxngy825i8xvzaK1Ncnkx0tBmb3k8DkEuqjKRkmtvNTjey2ZsNeh8Dw4lfKvbCOu9oeNx2TKm2vHqcRw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" } }, "node_modules/binary-extensions": { @@ -3068,24 +2998,6 @@ "node": ">=8" } }, - "node_modules/blessed": { - "version": "0.1.81", - "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", - "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", - "dev": true, - "bin": { - "blessed": "bin/tput.js" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/bodec": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", - "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", - "dev": true - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -3093,10 +3005,11 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -3114,9 +3027,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -3134,10 +3047,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -3164,27 +3078,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -3235,9 +3139,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001712", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz", - "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==", + "version": "1.0.30001765", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz", + "integrity": "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==", "dev": true, "funding": [ { @@ -3261,20 +3165,13 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/chalk": { @@ -3293,21 +3190,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/charm": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", - "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", - "dev": true - }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } + "license": "MIT" }, "node_modules/cheerio": { "version": "1.1.2", @@ -3352,25 +3240,20 @@ } }, "node_modules/cheerio/node_modules/undici": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", - "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", + "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", "license": "MIT", "engines": { "node": ">=20.18.1" } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -3383,6 +3266,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -3400,9 +3286,9 @@ } }, "node_modules/ci-info": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", - "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "dev": true, "funding": [ { @@ -3436,31 +3322,6 @@ "node": ">=0.8.0" } }, - "node_modules/cli-tableau": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", - "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", - "dev": true, - "dependencies": { - "chalk": "3.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/cli-tableau/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3517,13 +3378,13 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", - "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", + "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.24.4" + "browserslist": "^4.28.0" }, "funding": { "type": "opencollective", @@ -3543,12 +3404,6 @@ "node": ">=18.x" } }, - "node_modules/croner": { - "version": "4.1.97", - "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", - "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==", - "dev": true - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3592,31 +3447,16 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/culvert": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", - "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", - "dev": true - }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3626,29 +3466,31 @@ } }, "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/inspect-js" } }, "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" }, @@ -3659,13 +3501,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "dev": true, - "license": "MIT" - }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -3690,16 +3525,6 @@ "node": ">=18" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3740,26 +3565,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, + "node_modules/discord-api-types": { + "version": "0.38.37", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz", + "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==", "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/discord-api-types": { - "version": "0.37.119", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.119.tgz", - "integrity": "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg==", - "license": "MIT" + "workspaces": [ + "scripts/actions/documentation" + ] }, "node_modules/discord.js": { "version": "14.19.1", @@ -3788,12 +3601,6 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/discord.js/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", - "license": "MIT" - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -3864,13 +3671,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -3887,19 +3687,12 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.133", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.133.tgz", - "integrity": "sha512-mrR+g6Y1at0GKDlPlMMwLAkI6c47q3d7U/vKS3rGTa3V4xStu18oT4UCPg5ErcAhUqk7swSjXSPUGstOYd2qBw==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, "node_modules/encoding-sniffer": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", @@ -3913,30 +3706,6 @@ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" } }, - "node_modules/encoding-sniffer/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -3958,57 +3727,66 @@ } }, "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, + "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -4069,448 +3847,80 @@ "hasown": "^2.0.2" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, - "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "node": ">= 0.4" } }, - "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", - "cpu": [ - "x64" - ], + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "hasown": "^2.0.2" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" } }, - "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", - "cpu": [ - "arm64" - ], + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", - "cpu": [ - "ia32" - ], + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -4535,43 +3945,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, "node_modules/eslint": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", - "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.1", - "@eslint/core": "^0.16.0", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.38.0", - "@eslint/plugin-kit": "^0.4.0", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -4617,19 +4005,6 @@ } } }, - "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, "node_modules/eslint-import-context": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.8.tgz", @@ -4737,10 +4112,11 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -4758,6 +4134,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -4780,29 +4157,30 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, + "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", + "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", - "is-core-module": "^2.15.1", + "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", + "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "engines": { @@ -4843,14 +4221,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", - "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4892,44 +4270,45 @@ } }, "node_modules/eslint-plugin-unicorn": { - "version": "59.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-59.0.1.tgz", - "integrity": "sha512-EtNXYuWPUmkgSU2E7Ttn57LbRREQesIP1BiLn7OZLKodopKfDXfBUkC/0j6mpw2JExwf43Uf3qLSvrSvppgy8Q==", + "version": "62.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-62.0.0.tgz", + "integrity": "sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "@eslint-community/eslint-utils": "^4.5.1", - "@eslint/plugin-kit": "^0.2.7", - "ci-info": "^4.2.0", + "@babel/helper-validator-identifier": "^7.28.5", + "@eslint-community/eslint-utils": "^4.9.0", + "@eslint/plugin-kit": "^0.4.0", + "change-case": "^5.4.4", + "ci-info": "^4.3.1", "clean-regexp": "^1.0.0", - "core-js-compat": "^3.41.0", + "core-js-compat": "^3.46.0", "esquery": "^1.6.0", "find-up-simple": "^1.0.1", - "globals": "^16.0.0", + "globals": "^16.4.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "jsesc": "^3.1.0", "pluralize": "^8.0.0", "regexp-tree": "^0.1.27", - "regjsparser": "^0.12.0", - "semver": "^7.7.1", - "strip-indent": "^4.0.0" + "regjsparser": "^0.13.0", + "semver": "^7.7.3", + "strip-indent": "^4.1.1" }, "engines": { - "node": "^18.20.0 || ^20.10.0 || >=21.0.0" + "node": "^20.10.0 || >=21.0.0" }, "funding": { "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" }, "peerDependencies": { - "eslint": ">=9.22.0" + "eslint": ">=9.38.0" } }, "node_modules/eslint-plugin-unicorn/node_modules/globals": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", - "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "dev": true, "license": "MIT", "engines": { @@ -4968,33 +4347,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/core": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/eslint/node_modules/@eslint/plugin-kit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", - "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.16.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", @@ -5039,20 +4391,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -5107,33 +4445,16 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter2": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", - "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==", - "dev": true, - "license": "MIT" - }, "node_modules/expect-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", - "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.0.0" } }, - "node_modules/extrareqp2": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", - "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5143,42 +4464,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-patch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", - "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", "dev": true, - "license": "MIT" + "license": "Apache-2.0" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -5193,27 +4480,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fclone": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", - "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", - "dev": true, - "license": "MIT" - }, "node_modules/ffmpeg-static": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.2.0.tgz", - "integrity": "sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz", + "integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==", "hasInstallScript": true, + "license": "GPL-3.0-or-later", "dependencies": { "@derhuerst/http-basic": "^8.2.0", "env-paths": "^2.2.0", @@ -5329,63 +4601,20 @@ "dev": true, "license": "ISC" }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" + "is-callable": "^1.2.7" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/fsevents": { @@ -5413,15 +4642,18 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -5435,10 +4667,21 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5506,14 +4749,15 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -5535,33 +4779,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/get-uri": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", - "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/git-node-fs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", - "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", - "dev": true - }, - "node_modules/git-sha1": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", - "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", - "dev": true - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -5588,12 +4805,14 @@ } }, "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.3" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -5615,18 +4834,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5653,10 +4869,14 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -5682,6 +4902,7 @@ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -5712,9 +4933,9 @@ "license": "MIT" }, "node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -5726,14 +4947,14 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" + "domutils": "^3.2.2", + "entities": "^7.0.1" } }, "node_modules/htmlparser2/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -5742,30 +4963,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/http-response-object": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", @@ -5792,12 +4989,12 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -5863,55 +5060,51 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">= 12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ip-address/node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5921,12 +5114,16 @@ } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5945,13 +5142,14 @@ } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5991,6 +5189,7 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5999,10 +5198,11 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -6014,11 +5214,14 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, + "license": "MIT", "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -6029,12 +5232,14 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6052,14 +5257,40 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-glob": { @@ -6074,6 +5305,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", @@ -6096,12 +5340,14 @@ } }, "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6111,13 +5357,16 @@ } }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -6126,13 +5375,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -6142,12 +5405,14 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6157,12 +5422,15 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6172,12 +5440,13 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, + "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -6186,13 +5455,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6202,7 +5505,8 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", @@ -6252,25 +5556,10 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6281,22 +5570,6 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -6316,27 +5589,15 @@ } }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "devOptional": true, + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/js-git": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", - "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", - "dev": true, - "dependencies": { - "bodec": "^0.1.0", - "culvert": "^0.1.2", - "git-sha1": "^0.1.2", - "pako": "^0.2.5" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6345,10 +5606,11 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -6356,13 +5618,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, - "license": "MIT" - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6396,13 +5651,6 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "optional": true - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6417,9 +5665,9 @@ } }, "node_modules/keep-a-changelog": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/keep-a-changelog/-/keep-a-changelog-2.6.2.tgz", - "integrity": "sha512-YSebqNvqzsTcDYJ4jxO1w8vFa74J0R+8Ks0ZOtHQwdJho2EPuzOtAgRkpMd/0NxiQ0toqJgl7CPMz1urZs9pxg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/keep-a-changelog/-/keep-a-changelog-2.8.0.tgz", + "integrity": "sha512-RG9LRYRPy9jxBD/TRGoTwOAsWLF8iMcWFnQZP78MZk5a5TgCH9ceLy2wgpHGQHKp7jfAqS//1aj5Gu7U6/+PrA==", "dev": true, "license": "MIT", "dependencies": { @@ -6452,19 +5700,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libsodium": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz", - "integrity": "sha512-sZwRknt/tUpE2AwzHq3jEyUU5uvIZHtSssktXq7owd++3CSgn8RGrv6UZJJBpP7+iBghBqe7Z06/2M31rI2NKw==" - }, - "node_modules/libsodium-wrappers": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.15.tgz", - "integrity": "sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ==", - "dependencies": { - "libsodium": "^0.7.15" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -6481,9 +5716,10 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -6496,13 +5732,6 @@ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" }, - "node_modules/loupe": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", - "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6529,24 +5758,25 @@ "license": "MIT" }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" } }, "node_modules/make-dir": { @@ -6575,38 +5805,6 @@ "node": ">= 0.4" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -6621,9 +5819,9 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -6640,46 +5838,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/module-details-from-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", - "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", - "dev": true, - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -6731,53 +5894,17 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "dev": true, - "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", "dev": true, "license": "MIT", "dependencies": { @@ -6883,14 +6010,17 @@ } }, "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -6933,12 +6063,14 @@ } }, "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, @@ -6949,6 +6081,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -6966,6 +6109,24 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6996,77 +6157,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7176,30 +6266,6 @@ "node": ">= 0.8.0" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -7207,16 +6273,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7235,18 +6291,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidusage": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", - "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -7256,152 +6300,12 @@ "node": ">=4" } }, - "node_modules/pm2": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.8.tgz", - "integrity": "sha512-y7sO+UuGjfESK/ChRN+efJKAsHrBd95GY2p1GQfjVTtOfFtUfiW0NOuUhP5dN5QTF2F0EWcepgkLqbF32j90Iw==", - "dev": true, - "license": "AGPL-3.0", - "dependencies": { - "@pm2/agent": "~2.1.1", - "@pm2/io": "~6.1.0", - "@pm2/js-api": "~0.8.0", - "@pm2/pm2-version-check": "latest", - "ansis": "4.0.0-node10", - "async": "~3.2.6", - "blessed": "0.1.81", - "chokidar": "^3.5.3", - "cli-tableau": "^2.0.0", - "commander": "2.15.1", - "croner": "~4.1.92", - "dayjs": "~1.11.13", - "debug": "^4.3.7", - "enquirer": "2.3.6", - "eventemitter2": "5.0.1", - "fclone": "1.0.11", - "js-yaml": "~4.1.0", - "mkdirp": "1.0.4", - "needle": "2.4.0", - "pidusage": "~3.0", - "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.1", - "pm2-deploy": "~1.0.2", - "pm2-multimeter": "^0.1.2", - "promptly": "^2", - "semver": "^7.6.2", - "source-map-support": "0.5.21", - "sprintf-js": "1.1.2", - "vizion": "~2.2.1" - }, - "bin": { - "pm2": "bin/pm2", - "pm2-dev": "bin/pm2-dev", - "pm2-docker": "bin/pm2-docker", - "pm2-runtime": "bin/pm2-runtime" - }, - "engines": { - "node": ">=16.0.0" - }, - "optionalDependencies": { - "pm2-sysmonit": "^1.2.8" - } - }, - "node_modules/pm2-axon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", - "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "amp": "~0.3.1", - "amp-message": "~0.1.1", - "debug": "^4.3.1", - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=5" - } - }, - "node_modules/pm2-axon-rpc": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", - "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.1" - }, - "engines": { - "node": ">=5" - } - }, - "node_modules/pm2-deploy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", - "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", - "dev": true, - "dependencies": { - "run-series": "^1.1.8", - "tv4": "^1.3.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pm2-multimeter": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", - "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", - "dev": true, - "dependencies": { - "charm": "~0.1.1" - } - }, - "node_modules/pm2-sysmonit": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", - "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", - "dev": true, - "optional": true, - "dependencies": { - "async": "^3.2.0", - "debug": "^4.3.1", - "pidusage": "^2.0.21", - "systeminformation": "^5.7", - "tx2": "~1.0.4" - } - }, - "node_modules/pm2-sysmonit/node_modules/pidusage": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", - "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", - "dev": true, - "optional": true, - "dependencies": { - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pm2/node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/pm2/node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -7445,10 +6349,11 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", + "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -7460,10 +6365,11 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -7546,76 +6452,6 @@ "node": ">=0.4.0" } }, - "node_modules/promptly": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", - "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", - "dev": true, - "dependencies": { - "read": "^1.0.4" - } - }, - "node_modules/proxy-agent": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.3", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -7632,38 +6468,6 @@ "node": ">=6" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", - "dev": true, - "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -7689,6 +6493,29 @@ "node": ">=8.10.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexp-tree": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", @@ -7699,14 +6526,17 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "set-function-name": "^2.0.2" }, "engines": { @@ -7717,46 +6547,18 @@ } }, "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/require-in-the-middle": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", - "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -7788,19 +6590,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, "node_modules/rollup": { @@ -7843,58 +6635,17 @@ "fsevents": "~2.3.2" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/run-series": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", - "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -7923,15 +6674,33 @@ } ] }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -7943,18 +6712,13 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -7986,6 +6750,7 @@ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -7996,6 +6761,21 @@ "node": ">= 0.4" } }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8017,13 +6797,6 @@ "node": ">=8" } }, - "node_modules/shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -8107,13 +6880,6 @@ "dev": true, "license": "ISC" }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -8126,57 +6892,6 @@ "node": ">=10" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8203,12 +6918,6 @@ "source-map": "^0.6.0" } }, - "node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true - }, "node_modules/stable-hash-x": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.1.1.tgz", @@ -8227,100 +6936,48 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "safe-buffer": "~5.2.0" } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8330,15 +6987,20 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8360,32 +7022,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -8396,14 +7032,11 @@ } }, "node_modules/strip-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", - "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", + "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", "dev": true, "license": "MIT", - "dependencies": { - "min-indent": "^1.0.1" - }, "engines": { "node": ">=12" }, @@ -8424,26 +7057,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/superstruct": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", @@ -8478,98 +7091,19 @@ } }, "node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.4" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/systeminformation": { - "version": "5.24.8", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.24.8.tgz", - "integrity": "sha512-JzkqWPIHsOfgiQo2jNNSNEFGZOOh4/zgJPoRdfd62MtODWNZGkn42HDhKxjalf5sLDw++YDvWtis3Qh1G+ToUg==", - "dev": true, - "optional": true, - "os": [ - "darwin", - "linux", - "win32", - "freebsd", - "openbsd", - "netbsd", - "sunos", - "android" - ], - "bin": { - "systeminformation": "lib/cli.js" - }, - "engines": { - "node": ">=8.0.0" - }, - "funding": { - "type": "Buy me a coffee", - "url": "https://www.buymeacoffee.com/systeminfo" - } - }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "@pkgr/core": "^0.2.9" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/synckit" } }, "node_modules/tinybench": { @@ -8579,20 +7113,24 @@ "dev": true }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -8602,11 +7140,14 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -8617,9 +7158,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -8629,30 +7170,10 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", - "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, "license": "MIT", "engines": { @@ -8693,9 +7214,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -8757,13 +7278,13 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.25.0", + "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -8776,25 +7297,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tv4": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", - "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/tx2": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", - "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", - "dev": true, - "optional": true, - "dependencies": { - "json-stringify-safe": "^5.0.1" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8808,30 +7310,32 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -8841,17 +7345,19 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -8861,17 +7367,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -8886,9 +7393,9 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -8900,15 +7407,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz", - "integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", + "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.29.1", - "@typescript-eslint/parser": "8.29.1", - "@typescript-eslint/utils": "8.29.1" + "@typescript-eslint/eslint-plugin": "8.53.1", + "@typescript-eslint/parser": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/utils": "8.53.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8919,19 +7427,23 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8953,9 +7465,10 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" }, "node_modules/unrs-resolver": { "version": "1.9.0", @@ -8993,9 +7506,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -9014,7 +7527,7 @@ "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -9039,24 +7552,24 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -9065,14 +7578,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -9113,53 +7626,15 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite-node/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">=12.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "dev": true, - "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -9170,9 +7645,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -9183,51 +7658,50 @@ } }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.17.tgz", + "integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==", "dev": true, "license": "MIT", "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "@vitest/expect": "4.0.17", + "@vitest/mocker": "4.0.17", + "@vitest/pretty-format": "4.0.17", + "@vitest/runner": "4.0.17", + "@vitest/snapshot": "4.0.17", + "@vitest/spy": "4.0.17", + "@vitest/utils": "4.0.17", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.17", + "@vitest/browser-preview": "4.0.17", + "@vitest/browser-webdriverio": "4.0.17", + "@vitest/ui": "4.0.17", "happy-dom": "*", "jsdom": "*" }, @@ -9235,13 +7709,19 @@ "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { "optional": true }, "@vitest/ui": { @@ -9269,28 +7749,10 @@ "vitest": ">=3.0.0" } }, - "node_modules/vitest/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -9300,34 +7762,11 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vizion": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", - "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", - "dev": true, - "dependencies": { - "async": "^2.6.3", - "git-node-fs": "^1.0.0", - "ini": "^1.3.5", - "js-git": "^0.7.8" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/vizion/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" @@ -9336,18 +7775,6 @@ "node": ">=18" } }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -9373,32 +7800,17 @@ } }, "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -9407,128 +7819,97 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "siginfo": "^2.0.0", + "stackback": "0.0.2" }, - "engines": { - "node": ">=12" + "bin": { + "why-is-node-running": "cli.js" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "engines": { + "node": ">=8" } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 5a0f0608..0606c8d7 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "type": "module", "main": "./dist/main.js", "scripts": { - "build": "rm -rf dist && ./node_modules/.bin/tsc && npm run db:generate", + "build": "rm -rf dist && ./node_modules/.bin/tsc -p tsconfig.build.json && npm run db:generate", "commands:deploy": "node --env-file=.env . --deploy # TODO: Replace these with automatic command deployment", "commands:revoke": "node --env-file=.env . --revoke", "db:generate": "./node_modules/.bin/prisma generate --no-hints --schema ./prisma/schema.prisma", @@ -36,56 +36,52 @@ "docker:container": "docker run --tty --interactive --rm --mount type=bind,source=$PWD,destination=/cs-bot cs-bot-image", "docker:image": "docker build --tag cs-bot-image --label cs-bot .", "export-version": "./node_modules/.bin/genversion ./src/constants/version.ts -es", - "lint": "./node_modules/.bin/eslint .", + "lint": "./node_modules/.bin/eslint", "lint:fix": "npm run lint -- --fix", "release": "./node_modules/.bin/tsx ./scripts/release.ts", - "restart": "./node_modules/.bin/pm2 restart cs-bot", "setup": "npm ci && npm run export-version && npm run db:migrate && npm run build --production && npm run commands:deploy", - "start": "./node_modules/.bin/pm2 start ./dist/main.js --name cs-bot --node-args=\"--env-file=.env\"", - "stop": "./node_modules/.bin/pm2 delete cs-bot", + "start": "node --env-file=.env ./dist/main.js", "test": "./node_modules/.bin/vitest" }, "dependencies": { - "@discordjs/voice": "0.18.0", + "@discordjs/voice": "0.19.0", "@prisma/client": "6.13.0", "cheerio": "1.1.2", "cron": "4.3.3", "dectalk-tts": "1.0.1", "discord.js": "14.19.1", - "ffmpeg-static": "5.2.0", - "libsodium-wrappers": "0.7.15", + "ffmpeg-static": "5.3.0", "source-map-support": "0.5.21", "superstruct": "2.0.2", "tmp": "0.2.5" }, "devDependencies": { - "@stylistic/eslint-plugin": "4.4.1", + "@stylistic/eslint-plugin": "5.7.0", "@types/cheerio": "0.22.35", - "@types/node": "20.17.48", - "@types/semver": "7.7.0", + "@types/node": "20.19.30", + "@types/semver": "7.7.1", "@types/source-map-support": "0.5.10", "@types/tmp": "0.2.6", - "@vitest/coverage-istanbul": "3.2.4", - "eslint": "9.38.0", - "eslint-config-prettier": "9.1.0", + "@vitest/coverage-istanbul": "4.0.17", + "eslint": "9.39.2", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-file-progress": "3.0.2", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-prettier": "5.5.3", + "eslint-plugin-import": "2.32.0", + "eslint-plugin-prettier": "5.5.5", "eslint-plugin-promise": "7.2.1", - "eslint-plugin-unicorn": "59.0.1", + "eslint-plugin-unicorn": "62.0.0", "genversion": "3.2.0", - "keep-a-changelog": "2.6.2", - "nodemon": "3.1.10", - "pm2": "6.0.8", - "prettier": "3.6.2", + "jiti": "2.6.1", + "keep-a-changelog": "2.8.0", + "nodemon": "3.1.11", + "prettier": "3.8.0", "prettier-plugin-prisma": "5.0.0", "prisma": "6.9.0", - "semver": "7.7.2", - "tsx": "4.20.6", - "typescript": "5.8.3", - "typescript-eslint": "8.29.1", - "vitest": "3.2.4", + "semver": "7.7.3", + "tsx": "4.21.0", + "typescript": "5.9.3", + "typescript-eslint": "8.53.1", + "vitest": "4.0.17", "vitest-mock-extended": "3.1.0" }, "engines": { diff --git a/src/@types/eslint-plugin-promise.d.ts b/src/@types/eslint-plugin-promise.d.ts new file mode 100644 index 00000000..c2fda484 --- /dev/null +++ b/src/@types/eslint-plugin-promise.d.ts @@ -0,0 +1,15 @@ +// https://github.com/eslint-community/eslint-plugin-promise/issues/488 +declare module 'eslint-plugin-promise' { + import type { Config } from 'eslint/config'; + import type { ESLint } from 'eslint'; + import type { LegacyConfigObject, RulesConfig } from '@eslint/core'; + const plugin: { + rules: ESLint.Plugin['rules']; + rulesConfig: RulesConfig; + configs: { + recommended: LegacyConfigObject; + 'flat/recommended': Config; + }; + }; + export default plugin; +} diff --git a/src/commands/stats.ts b/src/commands/stats.ts index b765429d..b099bc6c 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -268,9 +268,7 @@ async function leaderboard( throw new UserMessageError(`No one is tracking the stat "${statName}"`); } - const scoresSorted = scoreboardEntries.sort( - (a: { score: number }, b: { score: number }) => b.score - a.score - ); + const scoresSorted = scoreboardEntries.toSorted((a, b) => b.score - a.score); const embedDescription = scoresSorted .map((entry: { userId: string; score: number }) => { diff --git a/src/roomFinder/search.test.ts b/src/roomFinder/search.test.ts index 11893e03..e0dfd7ef 100644 --- a/src/roomFinder/search.test.ts +++ b/src/roomFinder/search.test.ts @@ -169,6 +169,8 @@ describe('roomFinder search', () => { }); test('throws error when timeA is missing', async () => { + dbMock.buildings.findFirst.mockResolvedValue({ id: 1, name: 'TMCB' }); + await expect( lookup({ building: 'TMCB', @@ -179,6 +181,8 @@ describe('roomFinder search', () => { }); test('throws error when timeA is invalid format', async () => { + dbMock.buildings.findFirst.mockResolvedValue({ id: 1, name: 'TMCB' }); + await expect( lookup({ building: 'TMCB', @@ -236,6 +240,8 @@ describe('roomFinder search', () => { }); test('throws error when timeA is missing', async () => { + dbMock.buildings.findFirst.mockResolvedValue({ id: 1, name: 'TMCB' }); + await expect( lookup({ building: 'TMCB', @@ -247,6 +253,8 @@ describe('roomFinder search', () => { }); test('throws error when timeB is missing', async () => { + dbMock.buildings.findFirst.mockResolvedValue({ id: 1, name: 'TMCB' }); + await expect( lookup({ building: 'TMCB', @@ -260,6 +268,7 @@ describe('roomFinder search', () => { describe('lookup - when', () => { test('returns current events for specific room', async () => { + const mockBuilding = { id: 1, name: 'TMCB' }; const mockEvents = [ { id: 1, @@ -271,11 +280,12 @@ describe('roomFinder search', () => { room: { id: 1, number: '350', - building: { id: 1, name: 'TMCB' }, + building: mockBuilding, }, }, ]; + dbMock.buildings.findFirst.mockResolvedValue(mockBuilding); dbMock.events.findMany.mockResolvedValue(mockEvents); const result = await lookup({ @@ -305,6 +315,8 @@ describe('roomFinder search', () => { }); test('throws error when room is missing', async () => { + dbMock.buildings.findFirst.mockResolvedValue({ id: 1, name: 'TMCB' }); + await expect( lookup({ building: 'TMCB', diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..3ffc53e2 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"], + "exclude": ["**/*.test.*", "**/__mocks__/**"], + "compilerOptions": { + "outDir": "dist", + "removeComments": true, + "sourceMap": true, + } +} From 80caf7140b25886b04d0ed061e10822cd761d8d1 Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Sun, 1 Feb 2026 16:00:16 -0700 Subject: [PATCH 13/14] test: add mock for room scraper cron in ready tests --- src/events/ready.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/events/ready.test.ts b/src/events/ready.test.ts index b761f526..7927d021 100644 --- a/src/events/ready.test.ts +++ b/src/events/ready.test.ts @@ -55,6 +55,9 @@ const mockVerifyCommandDeployments = verifyCommandDeployments as Mock< // Mock the logger so nothing is printed vi.mock('../logger.js'); +// Mock the room scraper cron so it doesn't start during tests +vi.mock('../roomFinder/cron.js'); + // Import the code to test import { ready } from './ready.js'; From c517a76e330fd0fd817cf6c14bd2862f54e6fca9 Mon Sep 17 00:00:00 2001 From: Zack Yancey Date: Sun, 1 Feb 2026 16:19:26 -0700 Subject: [PATCH 14/14] Prettier blew up --- eslint.config.ts | 1 + package-lock.json | 17 +++++++++++++++++ package.json | 1 + src/roomFinder/api.ts | 6 +++--- src/roomFinder/cron.ts | 2 +- src/roomFinder/utils.ts | 2 +- 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 502fd2a3..29e97d89 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -77,6 +77,7 @@ export default defineConfig( rules: { // Handled by Prettier 'unicorn/no-nested-ternary': 'off', + 'unicorn/number-literal-case': 'off', // Overrides 'unicorn/filename-case': 'off', // We use camelCase diff --git a/package-lock.json b/package-lock.json index 26e95254..469cd16e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@types/tmp": "0.2.6", "@vitest/coverage-istanbul": "4.0.17", "eslint": "9.39.2", + "eslint-config-prettier": "10.1.8", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-file-progress": "3.0.2", "eslint-plugin-import": "2.32.0", @@ -4005,6 +4006,22 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-import-context": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.8.tgz", diff --git a/package.json b/package.json index 0606c8d7..c918c6b7 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@types/tmp": "0.2.6", "@vitest/coverage-istanbul": "4.0.17", "eslint": "9.39.2", + "eslint-config-prettier": "10.1.8", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-file-progress": "3.0.2", "eslint-plugin-import": "2.32.0", diff --git a/src/roomFinder/api.ts b/src/roomFinder/api.ts index 066e30b9..26d9474a 100644 --- a/src/roomFinder/api.ts +++ b/src/roomFinder/api.ts @@ -39,17 +39,17 @@ export async function searchNow(building: string): Promise { if (building.toUpperCase() === 'ANY' && rooms.length > 0) { const shuffled = rooms .map(value => ({ value, sort: Math.random() })) - .sort((a, b) => a.sort - b.sort) + .toSorted((a, b) => a.sort - b.sort) .map(({ value }) => value) .slice(0, Math.min(24, rooms.length)); // Sort by building name, then room number - shuffled.sort((a, b) => { + const sorted = shuffled.toSorted((a, b) => { if (a[1] !== b[1]) return a[1].localeCompare(b[1]); return a[0].localeCompare(b[0]); }); - return { Rooms: shuffled }; + return { Rooms: sorted }; } return { Rooms: rooms }; diff --git a/src/roomFinder/cron.ts b/src/roomFinder/cron.ts index 32d6b083..ad632c3b 100644 --- a/src/roomFinder/cron.ts +++ b/src/roomFinder/cron.ts @@ -22,7 +22,7 @@ import { info, error } from '../logger.js'; * - '0 0 1 * *' = Midnight on the 1st of every month * - '0 2 1,15 * *' = 2 AM on the 1st and 15th of every month */ -export function setupScraperCron(cronSchedule: string = '0 2 * * 0', yearTerm?: string): CronJob { +export function setupScraperCron(cronSchedule = '0 2 * * 0', yearTerm?: string): CronJob { info(`[CRON] Setting up room scraper cron: ${cronSchedule}`); const job = new CronJob( diff --git a/src/roomFinder/utils.ts b/src/roomFinder/utils.ts index 6dfc9e7c..18acedcd 100644 --- a/src/roomFinder/utils.ts +++ b/src/roomFinder/utils.ts @@ -104,7 +104,7 @@ export async function getAvailableRooms( } } - return availableRooms.sort((a, b) => { + return availableRooms.toSorted((a, b) => { if (a.buildingName !== b.buildingName) { return a.buildingName.localeCompare(b.buildingName); }