Compare commits

..

8 Commits

Author SHA1 Message Date
mightypanders
66b1a3bd7d update registration handler for new interface 2021-11-13 20:26:38 +01:00
mightypanders
ef4bb487df update discord adapter for new interface definition 2021-11-13 20:24:48 +01:00
mightypanders
25177b64ff update interface 2021-11-13 20:21:34 +01:00
mightypanders
efc60a4ccc adding test 2021-11-13 20:20:43 +01:00
mightypanders
69c057a191 adding jest 2021-11-13 20:20:18 +01:00
mightypanders
eb19430d37 shuffling things around 2021-11-13 13:48:38 +01:00
mightypanders
fa7ae7cbc1 Readme 2021-11-11 20:21:16 +01:00
mightypanders
db0849c6c2 Readme 2021-11-11 20:15:19 +01:00
10 changed files with 293 additions and 73 deletions

30
README.md Normal file
View File

@ -0,0 +1,30 @@
## Requirements
- yarn
- npm
## Environment Variables
- `BOT_TOKEN` -> acquire this from your Discord Application Page
- `GUILD_ID` -> the guild this bot should be active in. Still needs to be determined manually for the moment.
- `CLIENT_ID` -> not used right now
If you want to use this locally put these values into a file called `.env` in the project root.
```.env
BOT_TOKEN=<value here>
GUILD_ID=<value here>
CLIENT_ID=<value here>
```
## Usage
- Clone repo
- cd into dir
- `yarn install`
### Building
- `yarn run build`
### Start local instance
- `yarn run watch` for hot recompile on changes
- `yarn run monitor` for hot reload on recompile
### Start normal instance
- `yarn run start`

19
jest.config.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
'roots': [
'<rootDir>/tests',
'<rootDir>/server'
],
'transform': {
'^.+\\.tsx?$': 'ts-jest'
},
'testRegex': '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
'moduleFileExtensions': [
'ts',
'tsx',
'js',
'jsx',
'json',
'node'
],
};

View File

@ -9,7 +9,12 @@
"watch": "yarn run clean && yarn run build -- -w",
"monitor": "nodemon build/index.js",
"start": "yarn run build && node build/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
"lint":"eslint . --ext .ts",
"lint":"eslint . --ext .ts --fix",
"test": "jest",
"test-watch": "jest --watch",
"test-coverage": "jest --coverage"
},
"author": "",
"license": "ISC",
@ -31,9 +36,12 @@
"@tsconfig/recommended": "^1.0.1",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"jest-cli": "^27.3.1",
"nodemon": "^2.0.14",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"ts-jest": "^27.0.7"
}
}

View File

@ -1,13 +1,24 @@
import RegistrationHandler from "./RegistrationHandler"
export default class MuteHandler {
public mute(player: string): boolean {
console.log(`Performing mute wizardry on ${player}`)
const register = RegistrationHandler.Instance
const binding = register.getNameRegisteredForSteamUser(player)
console.log(`Performing mute wizardry on ${player}, ${JSON.stringify(binding)}`)
return true
}
public unmute(player: string): boolean {
const register = RegistrationHandler.Instance
const binding = register.getNameRegisteredForSteamUser(player)
console.log(`Performing unmute wizardry on ${player}`)
return true
}
public unmuteAll(): boolean {
const register = RegistrationHandler.Instance
const binding = register.getAllMappings()
console.log(`Performing unmute wizardry on all players`)
return true
}

View File

@ -1,36 +1,55 @@
export interface userNameBinding {
Steam: string,
Discord: string
}
export type Maybe<T> = T | undefined
import { GuildMember } from "discord.js"
import { Maybe, userNameBinding } from "./interfaces"
export default class RegistrationHandler {
private userRegister: userNameBinding[] = []
private static _instance: RegistrationHandler
public constructor() {
console.log('Setup RegistrationHandler')
}
public static get Instance() {
return this._instance || (this._instance = new this())
}
public register(discordname: string, steamname: string): boolean {
const binding: userNameBinding = {
Steam: steamname,
Discord: discordname
}
console.log(`Trying to register ${JSON.stringify(binding)}`)
public register(discordUser: GuildMember, steamname: string): boolean {
try {
let alreadyPresentBinding = this.userRegister.find(x => x.Discord == binding.Discord)
if (alreadyPresentBinding) {
console.log(`Binding already present: ${alreadyPresentBinding}, overwriting.`)
alreadyPresentBinding = binding
}
else {
this.userRegister.push(binding)
console.log(`Binding successfully added.`)
const binding: userNameBinding = {
Steam: steamname,
DiscordUser: discordUser
}
console.log(`Trying to register ${JSON.stringify(binding)}`)
let alreadyPresentBinding = this.userRegister.find(x => x.DiscordUser.user.username == binding.DiscordUser.user.username)
if (alreadyPresentBinding) {
console.log(`Binding already present: ${JSON.stringify(alreadyPresentBinding)}, overwriting.`)
alreadyPresentBinding = binding
}
else {
this.userRegister.push(binding)
console.log(`Binding successfully added.`)
}
} catch (error) {
console.error(error)
return false
}
return true
}
public removeUser(discordName: string): void { this.userRegister = this.userRegister.filter(x => x.Discord !== discordName) }
public getNameRegisteredForDiscordUser(discordUser: string): Maybe<userNameBinding> { return this.userRegister.find(x => x.Discord == discordUser) }
public getNameRegisteredForSteamUser(steamUser: string): Maybe<userNameBinding> { return this.userRegister.find(x => x.Steam == steamUser) }
public listRegisteredMembers(): string { return JSON.stringify(this.userRegister) }
private printHelpText(): void { }
public getAllMappings(): userNameBinding[] {
return this.userRegister
}
public removeUser(discordUser: GuildMember): void {
this.userRegister = this.userRegister.filter(x => x.DiscordUser.user.id !== discordUser.user.id)
}
public getNameRegisteredForDiscordUser(discordUser: GuildMember): Maybe<userNameBinding> {
return this.userRegister.find(x => x.DiscordUser.user.id == discordUser.user.id)
}
public getNameRegisteredForSteamUser(steamUser: string): Maybe<userNameBinding> {
return this.userRegister.find(x => x.Steam == steamUser)
}
public listRegisteredMembers(): string {
const output = this.userRegister.map(x => `${x.DiscordUser.user.username} : ${x.Steam}\n`)
return output.join()
}
private printHelpText(): string { return "" }
private buildHelpText(): string { return "" }
}

View File

@ -1,6 +1,4 @@
import { CommandInteraction } from "discord.js"
import { localized_string } from "./interfaces"
import RegistrationHandler from "./RegistrationHandler"
export const reactions = {
troll_grin: "U+1F92A",
@ -14,12 +12,6 @@ export const commands = {
SHOW_FOR_STEAM_COMMAND: "forsteam",
SHOW_COMMAND: "show"
}
export interface discordCommand {
name: string,
description: string
options?: any[]
performCommand(interaction: CommandInteraction, registration: RegistrationHandler): Promise<void>
}
export const msg_strings: localized_string = {
greeting: {
german: "Ich wurde neugestartet. Bitte registriert euch erneut, falls ihr automatisch gemutet werden wollt :)",

View File

@ -1,10 +1,11 @@
import { REST } from '@discordjs/rest'
import { SlashCommandBuilder } from '@discordjs/builders'
import { commands, discordCommand } from './constants'
import { commands } from './constants'
import { Routes } from 'discord-api-types/v9'
import { config } from './configuration'
import { Client, CommandInteraction, Intents, Interaction } from 'discord.js'
import { Client, CommandInteraction, GuildMember, Intents, Interaction } from 'discord.js'
import RegistrationHandler from './RegistrationHandler'
import { discordCommand } from './interfaces'
export default class DiscordAdapter {
private rest: REST
@ -13,7 +14,7 @@ export default class DiscordAdapter {
public constructor() {
this.rest = new REST({ version: '9' }).setToken(config.token)
this.client = new Client({ intents: [Intents.FLAGS.GUILDS] })
this.registration = new RegistrationHandler()
this.registration = RegistrationHandler.Instance
this.setupCallbacks(this.client)
this.client.login(config.token)
this.registerCommands(this.commandList)
@ -40,6 +41,13 @@ export default class DiscordAdapter {
}
public async echoes(interaction: CommandInteraction, registration: RegistrationHandler): Promise<void> {
const value = interaction.options.getString('input')
const member: GuildMember = <GuildMember>interaction.member
try {
console.log(`Member ${member.user.username} mute state: ${member.voice.mute}`)
member.voice.setMute(!member.voice.mute, "test")
} catch (error) {
console.error(error)
}
await interaction.reply(`This should be an echo: ${value}`)
}
public async listUsers(interaction: CommandInteraction, registration: RegistrationHandler): Promise<void> {
@ -47,28 +55,21 @@ export default class DiscordAdapter {
await interaction.reply(result)
}
public async removeUser(interaction: CommandInteraction, registration: RegistrationHandler): Promise<void> {
const discordUser = interaction.user
if (!discordUser.username || discordUser.username == "") {
await interaction.reply(`This user does not seem to have a username, can't delete`)
}
else {
registration.removeUser(discordUser.username)
await interaction.reply(`User has been removed`)
}
const discordUser: GuildMember = <GuildMember>interaction.member
registration.removeUser(discordUser)
await interaction.reply(`User has been removed`)
return
}
public async registerUser(interaction: CommandInteraction, registration: RegistrationHandler): Promise<void> {
const discordUser = interaction.user
const discordUser: GuildMember = <GuildMember>interaction.member
const steamNameToRegister = interaction.options.getString('steamname')
if (!discordUser.username || discordUser.username == "") {
await interaction.reply(`This user does not seem to have a username, can't register`)
}
else if (!steamNameToRegister) {
console.dir(discordUser)
if (!steamNameToRegister) {
await interaction.reply(`No steam name supplied, can't register`)
}
else {
registration.register(discordUser.username, steamNameToRegister)
await interaction.reply(`This should register user ${discordUser.username} with id ${discordUser.id} to use steamname: ${steamNameToRegister}`)
registration.register(discordUser, steamNameToRegister)
await interaction.reply(`This should register user ${discordUser.user.username} with id ${discordUser.user.id} to use steamname: ${steamNameToRegister}`)
}
return
}
@ -83,13 +84,9 @@ export default class DiscordAdapter {
return
}
public async show(interaction: CommandInteraction, registration: RegistrationHandler): Promise<void> {
const discordUser = interaction.user
if (!discordUser.username || discordUser.username == "") {
await interaction.reply(`This user does not seem to have a username, can't search`)
} else {
const result = registration.getNameRegisteredForDiscordUser(discordUser.username)
await interaction.reply(JSON.stringify(result, null, 2))
}
const discordUser: GuildMember = <GuildMember>interaction.member
const result = registration.getNameRegisteredForDiscordUser(discordUser)
await interaction.reply(JSON.stringify(result, null, 2))
return
}

View File

@ -1,3 +1,7 @@
import { CommandInteraction, GuildMember } from "discord.js"
import RegistrationHandler from "./RegistrationHandler"
export type Maybe<T> = T | undefined
export interface Player {
name: string
}
@ -8,3 +12,13 @@ export interface localized_string {
english: string
}
}
export interface userNameBinding {
Steam: string,
DiscordUser: GuildMember
}
export interface discordCommand {
name: string,
description: string
options?: any[]
performCommand(interaction: CommandInteraction, registration: RegistrationHandler): Promise<void>
}

View File

@ -0,0 +1,91 @@
import { GuildMember } from "discord.js"
import RegistrationHandler from "../server/RegistrationHandler"
const guildMember: GuildMember = <GuildMember><unknown>{
guild: {
id: 'guild_id',
name: 'Bot Playground',
icon: null,
features: [],
commands: { permissions: [], guild: [] },
members: { guild: [] },
channels: { guild: [] },
bans: { guild: [] },
roles: { guild: [] },
presences: {},
voiceStates: { guild: [] },
stageInstances: { guild: [] },
invites: { guild: [] },
deleted: false,
available: true,
shardId: 0,
splash: null,
banner: null,
description: null,
verificationLevel: 'NONE',
vanityURLCode: null,
nsfwLevel: 'DEFAULT',
discoverySplash: null,
memberCount: 2,
large: false,
applicationId: null,
afkTimeout: 300,
afkChannelId: null,
systemChannelId: 'channel_id',
premiumTier: 'NONE',
premiumSubscriptionCount: 0,
explicitContentFilter: 'DISABLED',
mfaLevel: 'NONE',
joinedTimestamp: 1636540056755,
defaultMessageNotifications: 'ALL_MESSAGES',
systemChannelFlags: { bitfield: 0 },
maximumMembers: 250000,
maximumPresences: null,
approximateMemberCount: null,
approximatePresenceCount: null,
vanityURLUses: null,
rulesChannelId: null,
publicUpdatesChannelId: null,
preferredLocale: 'en-US',
ownerId: 'ownerID',
emojis: { guild: [] },
stickers: { guild: [] }
},
joinedTimestamp: 1636539420924,
premiumSinceTimestamp: null,
deleted: false,
nickname: null,
pending: false,
_roles: [],
user: {
id: 'user_id',
bot: false,
system: false,
flags: { bitfield: 256 },
username: 'username',
discriminator: '0965',
avatar: 'avatar_string',
banner: undefined,
accentColor: undefined
},
avatar: null
}
const registeredUser = {
"Steam": "abc",
"DiscordUser": guildMember
}
test(`Instances`, () => {
const register = RegistrationHandler.Instance
expect(register).toBeDefined()
})
test(`Registration works`, () => {
const register = RegistrationHandler.Instance
register.register(<GuildMember>guildMember, "abc")
const result = register.getAllMappings()
console.log(JSON.stringify(result))
expect(result).toBeDefined()
expect(result).toEqual([registeredUser])
})

View File

@ -739,6 +739,14 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jest@^27.0.2":
version "27.0.2"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.0.2.tgz#ac383c4d4aaddd29bbf2b916d8d105c304a5fcd7"
integrity sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==
dependencies:
jest-diff "^27.0.0"
pretty-format "^27.0.0"
"@types/json-schema@^7.0.9":
version "7.0.9"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
@ -1171,6 +1179,13 @@ browserslist@^4.16.6:
node-releases "^2.0.1"
picocolors "^1.0.0"
bs-logger@0.x:
version "0.2.6"
resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==
dependencies:
fast-json-stable-stringify "2.x"
bser@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"
@ -1916,7 +1931,7 @@ fast-glob@^3.1.1:
merge2 "^1.3.0"
micromatch "^4.0.4"
fast-json-stable-stringify@^2.0.0:
fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
@ -2565,7 +2580,7 @@ jest-config@^27.3.1:
micromatch "^4.0.4"
pretty-format "^27.3.1"
jest-diff@^27.3.1:
jest-diff@^27.0.0, jest-diff@^27.3.1:
version "27.3.1"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.3.1.tgz#d2775fea15411f5f5aeda2a5e02c2f36440f6d55"
integrity sha512-PCeuAH4AWUo2O5+ksW4pL9v5xJAcIKPUPfIhZBcG1RKv/0+dvaWTQK1Nrau8d67dp65fOqbeMdoil+6PedyEPQ==
@ -2841,7 +2856,7 @@ jest-snapshot@^27.3.1:
pretty-format "^27.3.1"
semver "^7.3.2"
jest-util@^27.3.1:
jest-util@^27.0.0, jest-util@^27.3.1:
version "27.3.1"
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.3.1.tgz#a58cdc7b6c8a560caac9ed6bdfc4e4ff23f80429"
integrity sha512-8fg+ifEH3GDryLQf/eKZck1DEs2YuVPBCMOaHQxVVLmQwl/CDhWzrvChTX4efLZxGrw+AA0mSXv78cyytBt/uw==
@ -2969,7 +2984,7 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
json5@^2.1.2:
json5@2.x, json5@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
@ -3033,6 +3048,11 @@ lodash.isequal@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
lodash.memoize@4.x:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@ -3078,6 +3098,11 @@ make-dir@^3.0.0:
dependencies:
semver "^6.0.0"
make-error@1.x:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
makeerror@1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
@ -3443,7 +3468,7 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
pretty-format@^27.3.1:
pretty-format@^27.0.0, pretty-format@^27.3.1:
version "27.3.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.3.1.tgz#7e9486365ccdd4a502061fa761d3ab9ca1b78df5"
integrity sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA==
@ -3692,6 +3717,13 @@ semver-diff@^3.1.1:
dependencies:
semver "^6.3.0"
semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
semver@^5.7.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@ -3702,13 +3734,6 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@ -4006,6 +4031,20 @@ triple-beam@^1.2.0, triple-beam@^1.3.0:
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
ts-jest@^27.0.7:
version "27.0.7"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.7.tgz#fb7c8c8cb5526ab371bc1b23d06e745652cca2d0"
integrity sha512-O41shibMqzdafpuP+CkrOL7ykbmLh+FqQrXEmV9CydQ5JBk0Sj0uAEF5TNNe94fZWKm3yYvWa/IbyV4Yg1zK2Q==
dependencies:
bs-logger "0.x"
fast-json-stable-stringify "2.x"
jest-util "^27.0.0"
json5 "2.x"
lodash.memoize "4.x"
make-error "1.x"
semver "7.x"
yargs-parser "20.x"
ts-mixer@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.0.tgz#4e631d3a36e3fa9521b973b132e8353bc7267f9f"
@ -4338,7 +4377,7 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^20.2.2:
yargs-parser@20.x, yargs-parser@^20.2.2:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==