diff --git a/cloudformation/iam.yml b/cloudformation/iam.yml index cdad734f..c9b06908 100644 --- a/cloudformation/iam.yml +++ b/cloudformation/iam.yml @@ -16,6 +16,9 @@ Parameters: SqsQueueArn: Type: String +Conditions: + IsDev: !Equals [!Ref RunEnvironment, "dev"] + Resources: # Managed Policy for Common Lambda Permissions CommonLambdaManagedPolicy: @@ -241,6 +244,25 @@ Resources: ForAllValues:StringLike: ses:Recipients: - "*@illinois.edu" + - PolicyName: ses-sales + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - ses:SendEmail + - ses:SendRawEmail + Effect: Allow + Resource: "*" + Condition: + StringEquals: + ses:FromAddress: + Fn::Sub: "sales@${SesEmailDomain}" + ForAllValues:StringLike: + ses:Recipients: + - !If + - IsDev + - "*@illinois.edu" + - "*" EdgeLambdaIAMRole: diff --git a/cloudformation/main.yml b/cloudformation/main.yml index f6385bc9..da1585fa 100644 --- a/cloudformation/main.yml +++ b/cloudformation/main.yml @@ -258,6 +258,17 @@ Resources: FunctionResponseTypes: - ReportBatchItemFailures + SQSLambdaEventMappingSales: + Type: AWS::Lambda::EventSourceMapping + DependsOn: + - AppSqsLambdaFunction + Properties: + BatchSize: 5 + EventSourceArn: !GetAtt AppSQSQueues.Outputs.SalesEmailQueueArn + FunctionName: !Sub ${ApplicationPrefix}-sqs-lambda + FunctionResponseTypes: + - ReportBatchItemFailures + MembershipRecordsTable: Type: "AWS::DynamoDB::Table" DeletionPolicy: "Retain" @@ -765,3 +776,7 @@ Outputs: CloudfrontDistributionId: Description: Cloudfront Distribution ID Value: !GetAtt AppFrontendCloudfrontDistribution.Id + + SalesEmailQueueArn: + Description: Sales Email Queue Arn + Value: !GetAtt AppSQSQueues.Outputs.SalesEmailQueueArn diff --git a/cloudformation/sqs.yml b/cloudformation/sqs.yml index 6689e706..796692bc 100644 --- a/cloudformation/sqs.yml +++ b/cloudformation/sqs.yml @@ -24,6 +24,17 @@ Resources: - "AppDLQ" - "Arn" maxReceiveCount: 3 + SalesEmailQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: !Sub ${QueueName}-sales + VisibilityTimeout: !Ref MessageTimeout + RedrivePolicy: + deadLetterTargetArn: + Fn::GetAtt: + - "AppDLQ" + - "Arn" + maxReceiveCount: 3 Outputs: MainQueueArn: @@ -38,3 +49,9 @@ Outputs: Fn::GetAtt: - AppDLQ - Arn + SalesEmailQueueArn: + Description: Sales Email Queue Arn + Value: + Fn::GetAtt: + - SalesEmailQueue + - Arn diff --git a/src/api/functions/ses.ts b/src/api/functions/ses.ts index dd9fc4c8..7e94576c 100644 --- a/src/api/functions/ses.ts +++ b/src/api/functions/ses.ts @@ -1,5 +1,6 @@ import { SendRawEmailCommand } from "@aws-sdk/client-ses"; import { encode } from "base64-arraybuffer"; +import { AvailableSQSFunctions, SQSPayload } from "common/types/sqsMessage.js"; /** * Generates a SendRawEmailCommand for SES to send an email with an attached membership pass. @@ -132,3 +133,134 @@ ${encodedAttachment} }, }); } + +/** + * Generates a SendRawEmailCommand for SES to send a sales confirmation email + * + * @param payload - The SQS Payload for sending sale emails + * @param senderEmail - The email address of the sender with a verified identity in SES. + * @param imageBuffer - The normal image ticket/pass in ArrayBufferLike format. + * @returns The command to send the email via SES. + */ +export function generateSalesEmail( + payload: SQSPayload["payload"], + senderEmail: string, + imageBuffer: ArrayBufferLike, +): SendRawEmailCommand { + const encodedImage = encode(imageBuffer); + const boundary = "----BoundaryForEmail"; + + const subject = `Your ${payload.type === "merch" ? "order" : "ticket"} has been confirmed!`; + + const emailTemplate = ` + + + + ${subject} + + + + + + +
 
+ +
+
+

${subject}

+

+ Thank you for your purchase of ${payload.quantity} ${payload.itemName} ${payload.size ? `(size ${payload.size})` : ""}. + ${payload.type === "merch" ? "When picking up your order" : "When attending the event"}, show the attached QR code to our staff to verify your purchase. +

+ ${payload.customText ? `

${payload.customText}

` : ""} +

+ If you have any questions, feel free to ask on our Discord! +

+
+ Join our Discord +
+
+ + + + `; + + const rawEmail = ` +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="${boundary}" +From: ACM @ UIUC <${senderEmail}> +To: ${payload.email} +Subject: Your ACM @ UIUC Purchase + +--${boundary} +Content-Type: text/html; charset="UTF-8" + +${emailTemplate} + +--${boundary} +Content-Type: image/png +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="${payload.itemName}.png" + +${encodedImage} +--${boundary}--`.trim(); + + return new SendRawEmailCommand({ + RawMessage: { + Data: new TextEncoder().encode(rawEmail), + }, + }); +} diff --git a/src/api/package.json b/src/api/package.json index 7193ebe8..652e956f 100644 --- a/src/api/package.json +++ b/src/api/package.json @@ -46,6 +46,7 @@ "passkit-generator": "^3.3.1", "pino": "^9.6.0", "pluralize": "^8.0.0", + "qrcode": "^1.5.4", "stripe": "^17.6.0", "uuid": "^11.0.5", "zod": "^3.23.8", @@ -55,6 +56,7 @@ "devDependencies": { "@tsconfig/node22": "^22.0.0", "@types/aws-lambda": "^8.10.147", + "@types/qrcode": "^1.5.5", "nodemon": "^3.1.9" } } diff --git a/src/api/sqs/driver.ts b/src/api/sqs/driver.ts index faf682b3..8f5eb5eb 100644 --- a/src/api/sqs/driver.ts +++ b/src/api/sqs/driver.ts @@ -1,6 +1,9 @@ import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs"; import { environmentConfig, genericConfig } from "common/config.js"; -import { parseSQSPayload } from "common/types/sqsMessage.js"; +import { + AvailableSQSFunctions, + parseSQSPayload, +} from "common/types/sqsMessage.js"; const queueUrl = environmentConfig["dev"].SqsQueueUrl; const sqsClient = new SQSClient({ @@ -8,7 +11,7 @@ const sqsClient = new SQSClient({ }); const payload = parseSQSPayload({ - function: "ping", + function: AvailableSQSFunctions.Ping, payload: {}, metadata: { reqId: "1", diff --git a/src/api/sqs/index.ts b/src/api/sqs/index.ts index 5e8ac20d..94318cf8 100644 --- a/src/api/sqs/index.ts +++ b/src/api/sqs/index.ts @@ -20,6 +20,7 @@ import { import { ValidationError } from "../../common/errors/index.js"; import { RunEnvironment } from "../../common/roles.js"; import { environmentConfig } from "../../common/config.js"; +import { sendSaleEmailhandler } from "./sales.js"; export type SQSFunctionPayloadTypes = { [K in keyof typeof sqsPayloadSchemas]: SQSHandlerFunction; @@ -35,15 +36,21 @@ const handlers: SQSFunctionPayloadTypes = { [AvailableSQSFunctions.EmailMembershipPass]: emailMembershipPassHandler, [AvailableSQSFunctions.Ping]: pingHandler, [AvailableSQSFunctions.ProvisionNewMember]: provisionNewMemberHandler, + [AvailableSQSFunctions.SendSaleEmail]: sendSaleEmailhandler, }; export const runEnvironment = process.env.RunEnvironment as RunEnvironment; export const currentEnvironmentConfig = environmentConfig[runEnvironment]; +const restrictedQueues: Record = { + "infra-core-api-sqs-sales": [AvailableSQSFunctions.SendSaleEmail], +}; + export const handler = middy() .use(eventNormalizerMiddleware()) .use(sqsPartialBatchFailure()) .handler((event: SQSEvent, _context: Context, { signal: _signal }) => { const recordsPromises = event.Records.map(async (record, _index) => { + const sourceQueue = record.eventSourceARN.split(":").slice(-1)[0]; try { let parsedBody = parseSQSPayload(record.body); if (parsedBody instanceof ZodError) { @@ -56,6 +63,13 @@ export const handler = middy() }); } parsedBody = parsedBody as AnySQSPayload; + if ( + restrictedQueues[sourceQueue]?.includes(parsedBody.function) === false + ) { + throw new ValidationError({ + message: `Queue ${sourceQueue} is not permitted to call the function ${parsedBody.function}!`, + }); + } const childLogger = logger.child({ sqsMessageId: record.messageId, metadata: parsedBody.metadata, diff --git a/src/api/sqs/sales.ts b/src/api/sqs/sales.ts new file mode 100644 index 00000000..7cd0a13c --- /dev/null +++ b/src/api/sqs/sales.ts @@ -0,0 +1,24 @@ +import { AvailableSQSFunctions } from "common/types/sqsMessage.js"; +import { currentEnvironmentConfig, SQSHandlerFunction } from "./index.js"; +import { SESClient } from "@aws-sdk/client-ses"; +import QRCode from "qrcode"; +import { generateSalesEmail } from "api/functions/ses.js"; +import { genericConfig } from "common/config.js"; + +export const sendSaleEmailhandler: SQSHandlerFunction< + AvailableSQSFunctions.SendSaleEmail +> = async (payload, _metadata, logger) => { + const { qrCodeContent } = payload; + const senderEmail = `sales@${currentEnvironmentConfig["EmailDomain"]}`; + logger.info("Constructing QR Code..."); + const qrCode = await QRCode.toBuffer(qrCodeContent, { + errorCorrectionLevel: "H", + }); + logger.info("Constructing email..."); + const emailCommand = generateSalesEmail(payload, senderEmail, qrCode); + logger.info("Constructing email..."); + const sesClient = new SESClient({ region: genericConfig.AwsRegion }); + const response = await sesClient.send(emailCommand); + logger.info("Sent!"); + return response; +}; diff --git a/src/common/types/sqsMessage.ts b/src/common/types/sqsMessage.ts index 0d4a4151..bed7b13e 100644 --- a/src/common/types/sqsMessage.ts +++ b/src/common/types/sqsMessage.ts @@ -4,6 +4,7 @@ export enum AvailableSQSFunctions { Ping = "ping", EmailMembershipPass = "emailMembershipPass", ProvisionNewMember = "provisionNewMember", + SendSaleEmail = "sendSaleEmail", } const sqsMessageMetadataSchema = z.object({ @@ -42,12 +43,25 @@ export const sqsPayloadSchemas = { AvailableSQSFunctions.ProvisionNewMember, z.object({ email: z.string().email() }), ), + [AvailableSQSFunctions.SendSaleEmail]: createSQSSchema( + AvailableSQSFunctions.SendSaleEmail, + z.object({ + email: z.string().email(), + qrCodeContent: z.string().min(1), + itemName: z.string().min(1), + quantity: z.number().min(1), + size: z.string().optional(), + customText: z.string().optional(), + type: z.union([z.literal('event'), z.literal('merch')]) + }), + ), } as const; export const sqsPayloadSchema = z.discriminatedUnion("function", [ sqsPayloadSchemas[AvailableSQSFunctions.Ping], sqsPayloadSchemas[AvailableSQSFunctions.EmailMembershipPass], sqsPayloadSchemas[AvailableSQSFunctions.ProvisionNewMember], + sqsPayloadSchemas[AvailableSQSFunctions.SendSaleEmail], ] as const); export type SQSPayload = z.infer< diff --git a/yarn.lock b/yarn.lock index 2bbaf894..06164fb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3273,6 +3273,13 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2" integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ== +"@types/qrcode@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac" + integrity sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg== + dependencies: + "@types/node" "*" + "@types/qs@*": version "6.9.18" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2" @@ -4185,6 +4192,11 @@ camelcase-css@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + camelcase@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" @@ -4304,6 +4316,15 @@ clipboardy@3.0.0: execa "^5.1.1" is-wsl "^2.2.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -4598,6 +4619,11 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + decimal.js@^10.4.3: version "10.5.0" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.5.0.tgz#0f371c7cf6c4898ce0afb09836db73cd82010f22" @@ -4698,6 +4724,11 @@ diff@^5.2.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== +dijkstrajs@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23" + integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -5705,6 +5736,14 @@ find-my-way@^9.0.0: fast-querystring "^1.0.0" safe-regex2 "^4.0.0" +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -5849,7 +5888,7 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.5: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -6917,6 +6956,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -7533,6 +7579,13 @@ own-keys@^1.0.1: object-keys "^1.1.1" safe-push-apply "^1.0.0" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -7540,6 +7593,13 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" @@ -7547,6 +7607,11 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + package-json-from-dist@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" @@ -7724,6 +7789,11 @@ pluralize@^8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + polished@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/polished/-/polished-4.3.1.tgz#5a00ae32715609f83d89f6f31d0f0261c6170548" @@ -7900,6 +7970,15 @@ punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +qrcode@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.4.tgz#5cb81d86eb57c675febb08cf007fff963405da88" + integrity sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg== + dependencies: + dijkstrajs "^1.0.1" + pngjs "^5.0.0" + yargs "^15.3.1" + qs@^6.11.0: version "6.14.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" @@ -8223,6 +8302,11 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -8500,6 +8584,11 @@ serve@^14.2.4: serve-handler "6.1.6" update-check "1.5.4" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + set-cookie-parser@^2.6.0: version "2.7.1" resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" @@ -9806,6 +9895,11 @@ which-collection@^1.0.1, which-collection@^1.0.2: is-weakmap "^2.0.2" is-weakset "^2.0.3" +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + which-typed-array@^1.1.13, which-typed-array@^1.1.16, which-typed-array@^1.1.18, which-typed-array@^1.1.2: version "1.1.18" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.18.tgz#df2389ebf3fbb246a71390e90730a9edb6ce17ad" @@ -9861,6 +9955,15 @@ word-wrap@^1.2.5, word-wrap@~1.2.3: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -9914,6 +10017,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -9929,11 +10037,36 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"