From f52ab37a1725916b9042bee787b8f22fcca2495c Mon Sep 17 00:00:00 2001 From: tima101 Date: Tue, 23 Nov 2021 08:26:31 -0800 Subject: [PATCH] progress on #163 (book/11-end) --- book/.note | 12 - book/11-begin/.gitignore | 2 + .../11-begin/api/.elasticbeanstalk/config.yml | 19 + book/11-begin/api/.eslintignore | 3 + book/11-begin/api/.eslintrc.js | 26 + book/11-begin/api/.gitignore | 21 + book/11-begin/api/nodemon.json | 5 + book/11-begin/api/package.json | 72 + book/11-begin/api/server/api/index.ts | 17 + book/11-begin/api/server/api/public.ts | 58 + book/11-begin/api/server/api/team-leader.ts | 148 + book/11-begin/api/server/api/team-member.ts | 302 + book/11-begin/api/server/aws-s3.ts | 60 + book/11-begin/api/server/aws-ses.ts | 40 + book/11-begin/api/server/google-auth.ts | 111 + book/11-begin/api/server/logger.ts | 11 + book/11-begin/api/server/mailchimp.ts | 44 + book/11-begin/api/server/models/Discussion.ts | 207 + .../api/server/models/EmailTemplate.ts | 101 + book/11-begin/api/server/models/Invitation.ts | 234 + book/11-begin/api/server/models/Post.ts | 233 + book/11-begin/api/server/models/Team.ts | 298 + book/11-begin/api/server/models/User.ts | 484 ++ book/11-begin/api/server/passwordless-auth.ts | 121 + .../server/passwordless-token-mongostore.ts | 147 + book/11-begin/api/server/server.ts | 98 + book/11-begin/api/server/sockets.ts | 185 + book/11-begin/api/server/stripe.ts | 205 + book/11-begin/api/server/utils/slugify.ts | 43 + book/11-begin/api/server/utils/sum.ts | 5 + book/11-begin/api/static/robots.txt | 5 + .../api/test/server/utils/slugify.test.ts | 72 + .../api/test/server/utils/sum.test.ts | 9 + book/11-begin/api/tsconfig.json | 21 + book/11-begin/api/tsconfig.server.json | 9 + book/11-begin/api/yarn.lock | 6203 ++++++++++++++ book/11-begin/app/.babelrc | 10 + .../11-begin/app/.elasticbeanstalk/config.yml | 19 + book/11-begin/app/.eslintignore | 3 + book/11-begin/app/.eslintrc.js | 31 + book/11-begin/app/.gitignore | 21 + .../app/components/common/Confirmer.tsx | 71 + .../app/components/common/LoginButton.tsx | 90 + .../app/components/common/MemberChooser.tsx | 78 + .../app/components/common/MenuWithLinks.tsx | 95 + .../components/common/MenuWithMenuItems.tsx | 72 + .../app/components/common/Notifier.tsx | 53 + .../discussions/CreateDiscussionForm.tsx | 269 + .../discussions/DiscussionActionMenu.tsx | 179 + .../components/discussions/DiscussionList.tsx | 88 + .../discussions/DiscussionListItem.tsx | 74 + .../discussions/EditDiscussionForm.tsx | 205 + book/11-begin/app/components/layout/index.tsx | 268 + .../app/components/posts/PostContent.tsx | 23 + .../app/components/posts/PostDetail.tsx | 192 + .../app/components/posts/PostEditor.tsx | 303 + .../app/components/posts/PostForm.tsx | 225 + .../app/components/teams/InviteMember.tsx | 110 + book/11-begin/app/lib/api/makeQueryString.ts | 10 + book/11-begin/app/lib/api/public.ts | 38 + .../app/lib/api/sendRequestAndGetResponse.ts | 60 + book/11-begin/app/lib/api/team-leader.ts | 44 + book/11-begin/app/lib/api/team-member.ts | 98 + book/11-begin/app/lib/confirm.ts | 13 + book/11-begin/app/lib/isMobile.ts | 24 + book/11-begin/app/lib/notify.ts | 5 + book/11-begin/app/lib/resizeImage.ts | 59 + book/11-begin/app/lib/sharedStyles.ts | 27 + book/11-begin/app/lib/store/discussion.ts | 264 + book/11-begin/app/lib/store/index.ts | 186 + book/11-begin/app/lib/store/invitation.ts | 12 + book/11-begin/app/lib/store/post.ts | 79 + book/11-begin/app/lib/store/team.ts | 323 + book/11-begin/app/lib/store/user.ts | 110 + book/11-begin/app/lib/theme.ts | 39 + book/11-begin/app/lib/withAuth.tsx | 94 + book/11-begin/app/next-env.d.ts | 2 + book/11-begin/app/next.config.js | 18 + book/11-begin/app/nodemon.json | 5 + book/11-begin/app/package.json | 59 + book/11-begin/app/pages/_app.tsx | 153 + book/11-begin/app/pages/_document.tsx | 137 + book/11-begin/app/pages/billing.tsx | 309 + book/11-begin/app/pages/create-team.tsx | 208 + book/11-begin/app/pages/discussion.tsx | 314 + book/11-begin/app/pages/invitation.tsx | 96 + book/11-begin/app/pages/login-cached.tsx | 33 + book/11-begin/app/pages/login.tsx | 32 + book/11-begin/app/pages/team-settings.tsx | 396 + book/11-begin/app/pages/your-settings.tsx | 226 + .../IBM-Plex-Mono/IBMPlexMono-Regular.woff | Bin 0 -> 50536 bytes .../IBM-Plex-Mono/IBMPlexMono-Regular.woff2 | Bin 0 -> 35116 bytes .../public/fonts/Roboto/Roboto-Regular.woff | Bin 0 -> 93796 bytes .../public/fonts/Roboto/Roboto-Regular.woff2 | Bin 0 -> 66064 bytes book/11-begin/app/public/fonts/cdn.css | 18 + book/11-begin/app/public/fonts/server.css | 17 + book/11-begin/app/public/pepe.jpg | Bin 0 -> 100954 bytes book/11-begin/app/server/robots.txt | 5 + book/11-begin/app/server/routesWithCache.ts | 39 + book/11-begin/app/server/server.ts | 89 + .../app/server/setupSitemapAndRobots.ts | 50 + book/11-begin/app/tsconfig.json | 45 + book/11-begin/app/tsconfig.server.json | 13 + book/11-begin/app/yarn.lock | 7297 +++++++++++++++++ book/11-begin/lambda/.eslintignore | 3 + book/11-begin/lambda/.eslintrc.js | 28 + book/11-begin/lambda/.gitignore | 23 + book/11-begin/lambda/api | 1 + book/11-begin/lambda/handler.ts | 92 + book/11-begin/lambda/package.json | 55 + book/11-begin/lambda/serverless.yml | 29 + book/11-begin/lambda/tsconfig.json | 23 + book/11-begin/lambda/yarn.lock | 6365 ++++++++++++++ book/11-end/.gitignore | 2 + book/11-end/api/.elasticbeanstalk/config.yml | 19 + book/11-end/api/.eslintignore | 3 + book/11-end/api/.eslintrc.js | 26 + book/11-end/api/.gitignore | 21 + book/11-end/api/nodemon.json | 5 + book/11-end/api/package.json | 72 + book/11-end/api/server/api/index.ts | 17 + book/11-end/api/server/api/public.ts | 58 + book/11-end/api/server/api/team-leader.ts | 148 + book/11-end/api/server/api/team-member.ts | 302 + book/11-end/api/server/aws-s3.ts | 60 + book/11-end/api/server/aws-ses.ts | 40 + book/11-end/api/server/google-auth.ts | 111 + book/11-end/api/server/logger.ts | 11 + book/11-end/api/server/mailchimp.ts | 44 + book/11-end/api/server/models/Discussion.ts | 207 + .../11-end/api/server/models/EmailTemplate.ts | 101 + book/11-end/api/server/models/Invitation.ts | 234 + book/11-end/api/server/models/Post.ts | 233 + book/11-end/api/server/models/Team.ts | 298 + book/11-end/api/server/models/User.ts | 484 ++ book/11-end/api/server/passwordless-auth.ts | 121 + .../server/passwordless-token-mongostore.ts | 147 + book/11-end/api/server/server.ts | 98 + book/11-end/api/server/sockets.ts | 185 + book/11-end/api/server/stripe.ts | 204 + book/11-end/api/server/utils/slugify.ts | 46 + book/11-end/api/server/utils/sum.ts | 5 + book/11-end/api/static/robots.txt | 5 + .../api/test/server/utils/slugify.test.ts | 72 + book/11-end/api/test/server/utils/sum.test.ts | 9 + book/11-end/api/tsconfig.json | 21 + book/11-end/api/tsconfig.server.json | 9 + book/11-end/api/yarn.lock | 6203 ++++++++++++++ book/11-end/app/.babelrc | 10 + book/11-end/app/.elasticbeanstalk/config.yml | 19 + book/11-end/app/.eslintignore | 3 + book/11-end/app/.eslintrc.js | 31 + book/11-end/app/.gitignore | 21 + .../app/components/common/Confirmer.tsx | 71 + .../app/components/common/LoginButton.tsx | 90 + .../app/components/common/MemberChooser.tsx | 78 + .../app/components/common/MenuWithLinks.tsx | 95 + .../components/common/MenuWithMenuItems.tsx | 72 + .../11-end/app/components/common/Notifier.tsx | 53 + .../discussions/CreateDiscussionForm.tsx | 269 + .../discussions/DiscussionActionMenu.tsx | 179 + .../components/discussions/DiscussionList.tsx | 88 + .../discussions/DiscussionListItem.tsx | 74 + .../discussions/EditDiscussionForm.tsx | 205 + book/11-end/app/components/layout/index.tsx | 268 + .../app/components/posts/PostContent.tsx | 23 + .../app/components/posts/PostDetail.tsx | 192 + .../app/components/posts/PostEditor.tsx | 303 + book/11-end/app/components/posts/PostForm.tsx | 225 + .../app/components/teams/InviteMember.tsx | 110 + book/11-end/app/lib/api/makeQueryString.ts | 10 + book/11-end/app/lib/api/public.ts | 38 + .../app/lib/api/sendRequestAndGetResponse.ts | 60 + book/11-end/app/lib/api/team-leader.ts | 44 + book/11-end/app/lib/api/team-member.ts | 98 + book/11-end/app/lib/confirm.ts | 13 + book/11-end/app/lib/gtag.ts | 16 + book/11-end/app/lib/isMobile.ts | 24 + book/11-end/app/lib/notify.ts | 5 + book/11-end/app/lib/resizeImage.ts | 59 + book/11-end/app/lib/sharedStyles.ts | 27 + book/11-end/app/lib/store/discussion.ts | 264 + book/11-end/app/lib/store/index.ts | 186 + book/11-end/app/lib/store/invitation.ts | 12 + book/11-end/app/lib/store/post.ts | 79 + book/11-end/app/lib/store/team.ts | 323 + book/11-end/app/lib/store/user.ts | 110 + book/11-end/app/lib/theme.ts | 39 + book/11-end/app/lib/withAuth.tsx | 100 + book/11-end/app/next-env.d.ts | 2 + book/11-end/app/next.config.js | 18 + book/11-end/app/nodemon.json | 5 + book/11-end/app/package.json | 59 + book/11-end/app/pages/_app.tsx | 153 + book/11-end/app/pages/_document.tsx | 137 + book/11-end/app/pages/billing.tsx | 309 + book/11-end/app/pages/create-team.tsx | 208 + book/11-end/app/pages/discussion.tsx | 314 + book/11-end/app/pages/invitation.tsx | 96 + book/11-end/app/pages/login-cached.tsx | 33 + book/11-end/app/pages/login.tsx | 32 + book/11-end/app/pages/team-settings.tsx | 396 + book/11-end/app/pages/your-settings.tsx | 226 + .../IBM-Plex-Mono/IBMPlexMono-Regular.woff | Bin 0 -> 50536 bytes .../IBM-Plex-Mono/IBMPlexMono-Regular.woff2 | Bin 0 -> 35116 bytes .../public/fonts/Roboto/Roboto-Regular.woff | Bin 0 -> 93796 bytes .../public/fonts/Roboto/Roboto-Regular.woff2 | Bin 0 -> 66064 bytes book/11-end/app/public/fonts/cdn.css | 18 + book/11-end/app/public/fonts/server.css | 17 + book/11-end/app/public/pepe.jpg | Bin 0 -> 100954 bytes book/11-end/app/server/robots.txt | 5 + book/11-end/app/server/routesWithCache.ts | 39 + book/11-end/app/server/server.ts | 89 + .../app/server/setupSitemapAndRobots.ts | 50 + book/11-end/app/tsconfig.json | 45 + book/11-end/app/tsconfig.server.json | 13 + book/11-end/app/yarn.lock | 7297 +++++++++++++++++ book/11-end/lambda/.eslintignore | 3 + book/11-end/lambda/.eslintrc.js | 28 + book/11-end/lambda/.gitignore | 23 + book/11-end/lambda/api | 1 + book/11-end/lambda/handler.ts | 92 + book/11-end/lambda/package.json | 55 + book/11-end/lambda/serverless.yml | 29 + book/11-end/lambda/tsconfig.json | 23 + book/11-end/lambda/yarn.lock | 6365 ++++++++++++++ 226 files changed, 59550 insertions(+), 12 deletions(-) create mode 100644 book/11-begin/.gitignore create mode 100644 book/11-begin/api/.elasticbeanstalk/config.yml create mode 100644 book/11-begin/api/.eslintignore create mode 100644 book/11-begin/api/.eslintrc.js create mode 100644 book/11-begin/api/.gitignore create mode 100644 book/11-begin/api/nodemon.json create mode 100644 book/11-begin/api/package.json create mode 100644 book/11-begin/api/server/api/index.ts create mode 100644 book/11-begin/api/server/api/public.ts create mode 100644 book/11-begin/api/server/api/team-leader.ts create mode 100644 book/11-begin/api/server/api/team-member.ts create mode 100644 book/11-begin/api/server/aws-s3.ts create mode 100644 book/11-begin/api/server/aws-ses.ts create mode 100644 book/11-begin/api/server/google-auth.ts create mode 100644 book/11-begin/api/server/logger.ts create mode 100644 book/11-begin/api/server/mailchimp.ts create mode 100644 book/11-begin/api/server/models/Discussion.ts create mode 100644 book/11-begin/api/server/models/EmailTemplate.ts create mode 100644 book/11-begin/api/server/models/Invitation.ts create mode 100644 book/11-begin/api/server/models/Post.ts create mode 100644 book/11-begin/api/server/models/Team.ts create mode 100644 book/11-begin/api/server/models/User.ts create mode 100644 book/11-begin/api/server/passwordless-auth.ts create mode 100644 book/11-begin/api/server/passwordless-token-mongostore.ts create mode 100644 book/11-begin/api/server/server.ts create mode 100644 book/11-begin/api/server/sockets.ts create mode 100644 book/11-begin/api/server/stripe.ts create mode 100644 book/11-begin/api/server/utils/slugify.ts create mode 100644 book/11-begin/api/server/utils/sum.ts create mode 100644 book/11-begin/api/static/robots.txt create mode 100644 book/11-begin/api/test/server/utils/slugify.test.ts create mode 100644 book/11-begin/api/test/server/utils/sum.test.ts create mode 100644 book/11-begin/api/tsconfig.json create mode 100644 book/11-begin/api/tsconfig.server.json create mode 100644 book/11-begin/api/yarn.lock create mode 100644 book/11-begin/app/.babelrc create mode 100644 book/11-begin/app/.elasticbeanstalk/config.yml create mode 100644 book/11-begin/app/.eslintignore create mode 100644 book/11-begin/app/.eslintrc.js create mode 100644 book/11-begin/app/.gitignore create mode 100644 book/11-begin/app/components/common/Confirmer.tsx create mode 100644 book/11-begin/app/components/common/LoginButton.tsx create mode 100644 book/11-begin/app/components/common/MemberChooser.tsx create mode 100644 book/11-begin/app/components/common/MenuWithLinks.tsx create mode 100644 book/11-begin/app/components/common/MenuWithMenuItems.tsx create mode 100644 book/11-begin/app/components/common/Notifier.tsx create mode 100644 book/11-begin/app/components/discussions/CreateDiscussionForm.tsx create mode 100644 book/11-begin/app/components/discussions/DiscussionActionMenu.tsx create mode 100644 book/11-begin/app/components/discussions/DiscussionList.tsx create mode 100644 book/11-begin/app/components/discussions/DiscussionListItem.tsx create mode 100644 book/11-begin/app/components/discussions/EditDiscussionForm.tsx create mode 100644 book/11-begin/app/components/layout/index.tsx create mode 100644 book/11-begin/app/components/posts/PostContent.tsx create mode 100644 book/11-begin/app/components/posts/PostDetail.tsx create mode 100644 book/11-begin/app/components/posts/PostEditor.tsx create mode 100644 book/11-begin/app/components/posts/PostForm.tsx create mode 100644 book/11-begin/app/components/teams/InviteMember.tsx create mode 100644 book/11-begin/app/lib/api/makeQueryString.ts create mode 100644 book/11-begin/app/lib/api/public.ts create mode 100644 book/11-begin/app/lib/api/sendRequestAndGetResponse.ts create mode 100644 book/11-begin/app/lib/api/team-leader.ts create mode 100644 book/11-begin/app/lib/api/team-member.ts create mode 100644 book/11-begin/app/lib/confirm.ts create mode 100644 book/11-begin/app/lib/isMobile.ts create mode 100644 book/11-begin/app/lib/notify.ts create mode 100644 book/11-begin/app/lib/resizeImage.ts create mode 100644 book/11-begin/app/lib/sharedStyles.ts create mode 100644 book/11-begin/app/lib/store/discussion.ts create mode 100644 book/11-begin/app/lib/store/index.ts create mode 100644 book/11-begin/app/lib/store/invitation.ts create mode 100644 book/11-begin/app/lib/store/post.ts create mode 100644 book/11-begin/app/lib/store/team.ts create mode 100644 book/11-begin/app/lib/store/user.ts create mode 100644 book/11-begin/app/lib/theme.ts create mode 100644 book/11-begin/app/lib/withAuth.tsx create mode 100644 book/11-begin/app/next-env.d.ts create mode 100644 book/11-begin/app/next.config.js create mode 100644 book/11-begin/app/nodemon.json create mode 100644 book/11-begin/app/package.json create mode 100644 book/11-begin/app/pages/_app.tsx create mode 100644 book/11-begin/app/pages/_document.tsx create mode 100644 book/11-begin/app/pages/billing.tsx create mode 100644 book/11-begin/app/pages/create-team.tsx create mode 100644 book/11-begin/app/pages/discussion.tsx create mode 100644 book/11-begin/app/pages/invitation.tsx create mode 100644 book/11-begin/app/pages/login-cached.tsx create mode 100644 book/11-begin/app/pages/login.tsx create mode 100644 book/11-begin/app/pages/team-settings.tsx create mode 100644 book/11-begin/app/pages/your-settings.tsx create mode 100644 book/11-begin/app/public/fonts/IBM-Plex-Mono/IBMPlexMono-Regular.woff create mode 100644 book/11-begin/app/public/fonts/IBM-Plex-Mono/IBMPlexMono-Regular.woff2 create mode 100644 book/11-begin/app/public/fonts/Roboto/Roboto-Regular.woff create mode 100644 book/11-begin/app/public/fonts/Roboto/Roboto-Regular.woff2 create mode 100644 book/11-begin/app/public/fonts/cdn.css create mode 100644 book/11-begin/app/public/fonts/server.css create mode 100644 book/11-begin/app/public/pepe.jpg create mode 100644 book/11-begin/app/server/robots.txt create mode 100644 book/11-begin/app/server/routesWithCache.ts create mode 100644 book/11-begin/app/server/server.ts create mode 100644 book/11-begin/app/server/setupSitemapAndRobots.ts create mode 100644 book/11-begin/app/tsconfig.json create mode 100644 book/11-begin/app/tsconfig.server.json create mode 100644 book/11-begin/app/yarn.lock create mode 100644 book/11-begin/lambda/.eslintignore create mode 100644 book/11-begin/lambda/.eslintrc.js create mode 100644 book/11-begin/lambda/.gitignore create mode 120000 book/11-begin/lambda/api create mode 100644 book/11-begin/lambda/handler.ts create mode 100644 book/11-begin/lambda/package.json create mode 100644 book/11-begin/lambda/serverless.yml create mode 100644 book/11-begin/lambda/tsconfig.json create mode 100644 book/11-begin/lambda/yarn.lock create mode 100644 book/11-end/.gitignore create mode 100644 book/11-end/api/.elasticbeanstalk/config.yml create mode 100644 book/11-end/api/.eslintignore create mode 100644 book/11-end/api/.eslintrc.js create mode 100644 book/11-end/api/.gitignore create mode 100644 book/11-end/api/nodemon.json create mode 100644 book/11-end/api/package.json create mode 100644 book/11-end/api/server/api/index.ts create mode 100644 book/11-end/api/server/api/public.ts create mode 100644 book/11-end/api/server/api/team-leader.ts create mode 100644 book/11-end/api/server/api/team-member.ts create mode 100644 book/11-end/api/server/aws-s3.ts create mode 100644 book/11-end/api/server/aws-ses.ts create mode 100644 book/11-end/api/server/google-auth.ts create mode 100644 book/11-end/api/server/logger.ts create mode 100644 book/11-end/api/server/mailchimp.ts create mode 100644 book/11-end/api/server/models/Discussion.ts create mode 100644 book/11-end/api/server/models/EmailTemplate.ts create mode 100644 book/11-end/api/server/models/Invitation.ts create mode 100644 book/11-end/api/server/models/Post.ts create mode 100644 book/11-end/api/server/models/Team.ts create mode 100644 book/11-end/api/server/models/User.ts create mode 100644 book/11-end/api/server/passwordless-auth.ts create mode 100644 book/11-end/api/server/passwordless-token-mongostore.ts create mode 100644 book/11-end/api/server/server.ts create mode 100644 book/11-end/api/server/sockets.ts create mode 100644 book/11-end/api/server/stripe.ts create mode 100644 book/11-end/api/server/utils/slugify.ts create mode 100644 book/11-end/api/server/utils/sum.ts create mode 100644 book/11-end/api/static/robots.txt create mode 100644 book/11-end/api/test/server/utils/slugify.test.ts create mode 100644 book/11-end/api/test/server/utils/sum.test.ts create mode 100644 book/11-end/api/tsconfig.json create mode 100644 book/11-end/api/tsconfig.server.json create mode 100644 book/11-end/api/yarn.lock create mode 100644 book/11-end/app/.babelrc create mode 100644 book/11-end/app/.elasticbeanstalk/config.yml create mode 100644 book/11-end/app/.eslintignore create mode 100644 book/11-end/app/.eslintrc.js create mode 100644 book/11-end/app/.gitignore create mode 100644 book/11-end/app/components/common/Confirmer.tsx create mode 100644 book/11-end/app/components/common/LoginButton.tsx create mode 100644 book/11-end/app/components/common/MemberChooser.tsx create mode 100644 book/11-end/app/components/common/MenuWithLinks.tsx create mode 100644 book/11-end/app/components/common/MenuWithMenuItems.tsx create mode 100644 book/11-end/app/components/common/Notifier.tsx create mode 100644 book/11-end/app/components/discussions/CreateDiscussionForm.tsx create mode 100644 book/11-end/app/components/discussions/DiscussionActionMenu.tsx create mode 100644 book/11-end/app/components/discussions/DiscussionList.tsx create mode 100644 book/11-end/app/components/discussions/DiscussionListItem.tsx create mode 100644 book/11-end/app/components/discussions/EditDiscussionForm.tsx create mode 100644 book/11-end/app/components/layout/index.tsx create mode 100644 book/11-end/app/components/posts/PostContent.tsx create mode 100644 book/11-end/app/components/posts/PostDetail.tsx create mode 100644 book/11-end/app/components/posts/PostEditor.tsx create mode 100644 book/11-end/app/components/posts/PostForm.tsx create mode 100644 book/11-end/app/components/teams/InviteMember.tsx create mode 100644 book/11-end/app/lib/api/makeQueryString.ts create mode 100644 book/11-end/app/lib/api/public.ts create mode 100644 book/11-end/app/lib/api/sendRequestAndGetResponse.ts create mode 100644 book/11-end/app/lib/api/team-leader.ts create mode 100644 book/11-end/app/lib/api/team-member.ts create mode 100644 book/11-end/app/lib/confirm.ts create mode 100644 book/11-end/app/lib/gtag.ts create mode 100644 book/11-end/app/lib/isMobile.ts create mode 100644 book/11-end/app/lib/notify.ts create mode 100644 book/11-end/app/lib/resizeImage.ts create mode 100644 book/11-end/app/lib/sharedStyles.ts create mode 100644 book/11-end/app/lib/store/discussion.ts create mode 100644 book/11-end/app/lib/store/index.ts create mode 100644 book/11-end/app/lib/store/invitation.ts create mode 100644 book/11-end/app/lib/store/post.ts create mode 100644 book/11-end/app/lib/store/team.ts create mode 100644 book/11-end/app/lib/store/user.ts create mode 100644 book/11-end/app/lib/theme.ts create mode 100644 book/11-end/app/lib/withAuth.tsx create mode 100644 book/11-end/app/next-env.d.ts create mode 100644 book/11-end/app/next.config.js create mode 100644 book/11-end/app/nodemon.json create mode 100644 book/11-end/app/package.json create mode 100644 book/11-end/app/pages/_app.tsx create mode 100644 book/11-end/app/pages/_document.tsx create mode 100644 book/11-end/app/pages/billing.tsx create mode 100644 book/11-end/app/pages/create-team.tsx create mode 100644 book/11-end/app/pages/discussion.tsx create mode 100644 book/11-end/app/pages/invitation.tsx create mode 100644 book/11-end/app/pages/login-cached.tsx create mode 100644 book/11-end/app/pages/login.tsx create mode 100644 book/11-end/app/pages/team-settings.tsx create mode 100644 book/11-end/app/pages/your-settings.tsx create mode 100644 book/11-end/app/public/fonts/IBM-Plex-Mono/IBMPlexMono-Regular.woff create mode 100644 book/11-end/app/public/fonts/IBM-Plex-Mono/IBMPlexMono-Regular.woff2 create mode 100644 book/11-end/app/public/fonts/Roboto/Roboto-Regular.woff create mode 100644 book/11-end/app/public/fonts/Roboto/Roboto-Regular.woff2 create mode 100644 book/11-end/app/public/fonts/cdn.css create mode 100644 book/11-end/app/public/fonts/server.css create mode 100644 book/11-end/app/public/pepe.jpg create mode 100644 book/11-end/app/server/robots.txt create mode 100644 book/11-end/app/server/routesWithCache.ts create mode 100644 book/11-end/app/server/server.ts create mode 100644 book/11-end/app/server/setupSitemapAndRobots.ts create mode 100644 book/11-end/app/tsconfig.json create mode 100644 book/11-end/app/tsconfig.server.json create mode 100644 book/11-end/app/yarn.lock create mode 100644 book/11-end/lambda/.eslintignore create mode 100644 book/11-end/lambda/.eslintrc.js create mode 100644 book/11-end/lambda/.gitignore create mode 120000 book/11-end/lambda/api create mode 100644 book/11-end/lambda/handler.ts create mode 100644 book/11-end/lambda/package.json create mode 100644 book/11-end/lambda/serverless.yml create mode 100644 book/11-end/lambda/tsconfig.json create mode 100644 book/11-end/lambda/yarn.lock diff --git a/book/.note b/book/.note index 546de438..2aa0b276 100644 --- a/book/.note +++ b/book/.note @@ -1,15 +1,3 @@ -helmet and compression for Express servers - -SEO (robots and sitemap) for APP and API - -Logs for API with winston - -Google Analytics in Chapter 12 - -deleteFiles after introducing Post - ---- - makeQueryString when it is needed Since `href` has to be URI-encoded, we convert `redirectUrlAfterLogin` string into URI-encoded string using `makeQueryString` method. We define this method in a new file `book/5-begin/app/lib/api/makeQueryString.ts`: diff --git a/book/11-begin/.gitignore b/book/11-begin/.gitignore new file mode 100644 index 00000000..97aca2ea --- /dev/null +++ b/book/11-begin/.gitignore @@ -0,0 +1,2 @@ +.env +node_modules \ No newline at end of file diff --git a/book/11-begin/api/.elasticbeanstalk/config.yml b/book/11-begin/api/.elasticbeanstalk/config.yml new file mode 100644 index 00000000..6f0d072e --- /dev/null +++ b/book/11-begin/api/.elasticbeanstalk/config.yml @@ -0,0 +1,19 @@ +branch-defaults: + default: + environment: api-saas-boilerplate +environment-defaults: + app-saas-boilerplate: + branch: null + repository: null +global: + application_name: book + default_ec2_keyname: null + default_platform: Node.js 12 running on 64bit Amazon Linux 2 + default_region: us-east-1 + include_git_submodules: true + instance_profile: null + platform_name: null + platform_version: null + profile: null + sc: null + workspace_type: Application diff --git a/book/11-begin/api/.eslintignore b/book/11-begin/api/.eslintignore new file mode 100644 index 00000000..ff4515a6 --- /dev/null +++ b/book/11-begin/api/.eslintignore @@ -0,0 +1,3 @@ +.next +production-server +node_modules diff --git a/book/11-begin/api/.eslintrc.js b/book/11-begin/api/.eslintrc.js new file mode 100644 index 00000000..d8ca7f23 --- /dev/null +++ b/book/11-begin/api/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + parser: "@typescript-eslint/parser", + extends: ["plugin:@typescript-eslint/recommended", "prettier"], + env: { + "es6": true, + "node": true, + }, + plugins: ["prettier"], + rules: { + 'prettier/prettier': [ + 'error', + { + singleQuote: true, + trailingComma: 'all', + arrowParens: 'always', + printWidth: 100, + semi: true, + }, + ], + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'prefer-arrow-callback': 'error', + '@typescript-eslint/explicit-module-boundary-types': 'off', + }, +} \ No newline at end of file diff --git a/book/11-begin/api/.gitignore b/book/11-begin/api/.gitignore new file mode 100644 index 00000000..69968ac7 --- /dev/null +++ b/book/11-begin/api/.gitignore @@ -0,0 +1,21 @@ +*~ +*.swp +tmp/ +npm-debug.log +.DS_Store + + + +.build/* +.next +.vscode/ +node_modules/ +.coverage +.env +now.json +.note + +compiled/ +production-server/ + +yarn-error.log diff --git a/book/11-begin/api/nodemon.json b/book/11-begin/api/nodemon.json new file mode 100644 index 00000000..61838926 --- /dev/null +++ b/book/11-begin/api/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["server"], + "exec": "ts-node --project tsconfig.server.json", + "ext": "ts" +} diff --git a/book/11-begin/api/package.json b/book/11-begin/api/package.json new file mode 100644 index 00000000..3f08f015 --- /dev/null +++ b/book/11-begin/api/package.json @@ -0,0 +1,72 @@ +{ + "name": "11-begin-api", + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": "12.16.1", + "yarn": "1.22.5" + }, + "scripts": { + "dev": "nodemon server/server.ts", + "lint": "eslint . --ext .ts,.tsx", + "test": "jest", + "postinstall": "rm -rf production-server/", + "build": "tsc --project tsconfig.server.json", + "start": "node production-server/server.js" + }, + "jest": { + "preset": "ts-jest", + "testPathIgnorePatterns": [ + "production-server" + ], + "testEnvironment": "node" + }, + "dependencies": { + "aws-sdk": "^2.796.0", + "bcrypt": "^5.0.0", + "compression": "^1.7.4", + "connect-mongo": "^3.2.0", + "cors": "^2.8.5", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "express-session": "^1.17.1", + "he": "^1.2.0", + "helmet": "4.1.0-rc.2", + "highlight.js": "^10.5.0", + "lodash": "^4.17.20", + "marked": "^1.2.7", + "mongoose": "^5.10.15", + "node-fetch": "^2.6.1", + "passport": "^0.4.1", + "passport-google-oauth": "^2.0.0", + "passwordless": "^1.1.3", + "passwordless-tokenstore": "^0.0.10", + "socket.io": "^3.0.3", + "stripe": "^8.129.0", + "typescript": "^4.1.3", + "winston": "^3.3.3" + }, + "devDependencies": { + "@types/connect-mongo": "^3.1.3", + "@types/dotenv": "^8.2.0", + "@types/express": "^4.11.1", + "@types/express-session": "^1.15.8", + "@types/jest": "^22.2.3", + "@types/lodash": "^4.14.108", + "@types/mongoose": "^5.5.43", + "@types/node": "^12.12.2", + "@types/node-fetch": "^1.6.9", + "@types/passport": "^0.4.5", + "@types/socket.io": "^1.4.33", + "@typescript-eslint/eslint-plugin": "^4.2.0", + "@typescript-eslint/parser": "^4.2.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^7.1.0", + "eslint-plugin-prettier": "^3.3.1", + "jest": "^26.4.2", + "nodemon": "^2.0.7", + "prettier": "^2.2.1", + "ts-jest": "^26.4.4", + "ts-node": "^9.1.1" + } +} diff --git a/book/11-begin/api/server/api/index.ts b/book/11-begin/api/server/api/index.ts new file mode 100644 index 00000000..797beb5c --- /dev/null +++ b/book/11-begin/api/server/api/index.ts @@ -0,0 +1,17 @@ +import * as express from 'express'; + +import publicExpressRoutes from './public'; +import teamMemberExpressRoutes from './team-member'; +import teamLeaderApi from './team-leader'; + +function handleError(err, _, res, __) { + console.error(err.stack); + + res.json({ error: err.message || err.toString() }); +} + +export default function api(server: express.Express) { + server.use('/api/v1/public', publicExpressRoutes, handleError); + server.use('/api/v1/team-member', teamMemberExpressRoutes, handleError); + server.use('/api/v1/team-leader', teamLeaderApi, handleError); +} diff --git a/book/11-begin/api/server/api/public.ts b/book/11-begin/api/server/api/public.ts new file mode 100644 index 00000000..9dd02377 --- /dev/null +++ b/book/11-begin/api/server/api/public.ts @@ -0,0 +1,58 @@ +import * as express from 'express'; + +import User from '../models/User'; +import Invitation from '../models/Invitation'; + +const router = express.Router(); + +router.get('/get-user', (req, res) => { + console.log(req.user); + res.json({ user: req.user || null }); +}); + +router.post('/get-user-by-slug', async (req, res, next) => { + console.log('Express route: /get-user-by-slug'); + + // req.session.foo = 'bar'; + + try { + const { slug } = req.body; + + const user = await User.getUserBySlug({ slug }); + + res.json({ user }); + } catch (err) { + next(err); + } +}); + +router.get('/invitations/accept-and-get-team-by-token', async (req, res, next) => { + const token = req.query.token as string; + + try { + const team = await Invitation.getTeamByToken({ token }); + + if (req.user) { + await Invitation.addUserToTeam({ token, user: req.user }); + } + + res.json({ team }); + } catch (err) { + next(err); + } +}); + +router.post('/invitations/remove-invitation-if-member-added', async (req, res, next) => { + try { + const team = await Invitation.removeIfMemberAdded({ + token: req.body.token, + userId: req.user.id, + }); + + res.json({ team }); + } catch (err) { + next(err); + } +}); + +export default router; diff --git a/book/11-begin/api/server/api/team-leader.ts b/book/11-begin/api/server/api/team-leader.ts new file mode 100644 index 00000000..fabeb733 --- /dev/null +++ b/book/11-begin/api/server/api/team-leader.ts @@ -0,0 +1,148 @@ +import * as express from 'express'; + +import Invitation from '../models/Invitation'; +import Team from '../models/Team'; +import User from '../models/User'; +import { createSession } from '../stripe'; + +const router = express.Router(); + +router.use((req, res, next) => { + console.log('team leader API', req.path); + + if (!req.user) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + + next(); +}); + +router.post('/teams/add', async (req, res, next) => { + try { + const { name, avatarUrl } = req.body; + + console.log(`Express route: ${name}, ${avatarUrl}`); + + const team = await Team.addTeam({ userId: req.user.id, name, avatarUrl }); + + res.json(team); + } catch (err) { + next(err); + } +}); + +router.post('/teams/update', async (req, res, next) => { + try { + const { teamId, name, avatarUrl } = req.body; + + // console.log(req.user.id, typeof req.user.id); + // console.log(req.user._id, typeof req.user._id); + + const team = await Team.updateTeam({ + userId: req.user.id, + teamId, + name, + avatarUrl, + }); + + res.json(team); + } catch (err) { + next(err); + } +}); + +router.get('/teams/get-invitations-for-team', async (req, res, next) => { + try { + const users = await Invitation.getTeamInvitations({ + userId: req.user.id, + teamId: req.query.teamId as string, + }); + + res.json({ users }); + } catch (err) { + next(err); + } +}); + +router.post('/teams/invite-member', async (req, res, next) => { + try { + const { teamId, email } = req.body; + + const newInvitation = await Invitation.add({ userId: req.user.id, teamId, email }); + + res.json({ newInvitation }); + } catch (err) { + next(err); + } +}); + +router.post('/teams/remove-member', async (req, res, next) => { + try { + const { teamId, userId } = req.body; + + await Team.removeMember({ teamLeaderId: req.user.id, teamId, userId }); + + res.json({ done: 1 }); + } catch (err) { + next(err); + } +}); + +router.post('/stripe/fetch-checkout-session', async (req, res, next) => { + try { + const { mode, teamId } = req.body; + + const user = await User.findById(req.user.id).select(['stripeCustomer', 'email']).setOptions({ lean: true }); + + const team = await Team.findById(teamId) + .select(['stripeSubscription', 'slug', 'teamLeaderId']) + .setOptions({ lean: true }); + + if (!user || !team || team.teamLeaderId !== req.user.id) { + throw new Error('Permission denied'); + } + + const session = await createSession({ + mode, + userId: user._id.toString(), + userEmail: user.email, + teamId, + teamSlug: team.slug, + customerId: (user.stripeCustomer && user.stripeCustomer.id) || undefined, + subscriptionId: (team.stripeSubscription && team.stripeSubscription.id) || undefined, + }); + + res.json({ sessionId: session.id }); + } catch (err) { + next(err); + } +}); + +router.post('/cancel-subscription', async (req, res, next) => { + const { teamId } = req.body; + + try { + const { isSubscriptionActive } = await Team.cancelSubscription({ + teamLeaderId: req.user.id, + teamId, + }); + + res.json({ isSubscriptionActive }); + } catch (err) { + next(err); + } +}); + +router.get('/get-list-of-invoices-for-customer', async (req, res, next) => { + try { + const { stripeListOfInvoices } = await User.getListOfInvoicesForCustomer({ + userId: req.user.id, + }); + res.json({ stripeListOfInvoices }); + } catch (err) { + next(err); + } +}); + +export default router; diff --git a/book/11-begin/api/server/api/team-member.ts b/book/11-begin/api/server/api/team-member.ts new file mode 100644 index 00000000..b4a8465b --- /dev/null +++ b/book/11-begin/api/server/api/team-member.ts @@ -0,0 +1,302 @@ +import * as express from 'express'; + +import { signRequestForUpload } from '../aws-s3'; + +import User from '../models/User'; +import Team from '../models/Team'; +import Invitation from '../models/Invitation'; +import Discussion from '../models/Discussion'; +import Post from '../models/Post'; + +import { + discussionAdded, + discussionDeleted, + discussionEdited, + postAdded, + postDeleted, + postEdited, +} from '../sockets'; + +const router = express.Router(); + +router.use((req, res, next) => { + console.log('team member API', req.path); + if (!req.user) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + + next(); +}); + +// Get signed request from AWS S3 server +router.post('/aws/get-signed-request-for-upload-to-s3', async (req, res, next) => { + try { + const { fileName, fileType, prefix, bucket } = req.body; + + const returnData = await signRequestForUpload({ + fileName, + fileType, + prefix, + bucket, + }); + + console.log(bucket); + + res.json(returnData); + } catch (err) { + next(err); + } +}); + +router.post('/user/update-profile', async (req, res, next) => { + try { + const { name, avatarUrl } = req.body; + + const updatedUser = await User.updateProfile({ + userId: req.user.id, + name, + avatarUrl, + }); + + res.json({ updatedUser }); + } catch (err) { + next(err); + } +}); + +router.post('/user/toggle-theme', async (req, res, next) => { + try { + const { darkTheme } = req.body; + + await User.toggleTheme({ userId: req.user.id, darkTheme }); + + res.json({ done: 1 }); + } catch (err) { + next(err); + } +}); + +async function loadDiscussionsData(team, userId, body) { + const { discussionSlug } = body; + + if (!discussionSlug) { + return []; + } + + const { discussions } = await Discussion.getList({ + userId, + teamId: team._id, + }); + + for (const discussion of discussions) { + if (discussion.slug === discussionSlug) { + Object.assign(discussion, { + initialPosts: await Post.getList({ + userId, + discussionId: discussion._id, + }), + }); + + break; + } + } + + return discussions; +} + +async function loadTeamData(team, userId, body) { + const initialMembers = await User.getMembersForTeam({ + userId, + teamId: team._id, + }); + + let initialInvitations = []; + if (userId === team.teamLeaderId) { + initialInvitations = await Invitation.getTeamInvitations({ + userId, + teamId: team._id, + }); + } + + console.log(`initialMembers:${initialMembers}`); + + const initialDiscussions = await loadDiscussionsData(team, userId, body); + + const data: any = { initialMembers, initialInvitations, initialDiscussions }; + + // console.log(`Express route:${data.initialPosts}`); + + return data; +} + +router.post('/get-initial-data', async (req, res, next) => { + try { + const teams = await Team.getAllTeamsForUser(req.user.id); + + let selectedTeamSlug = req.body.teamSlug; + if (!selectedTeamSlug && teams && teams.length > 0) { + selectedTeamSlug = teams[0].slug; + } + + for (const team of teams) { + if (team.slug === selectedTeamSlug) { + Object.assign(team, await loadTeamData(team, req.user.id, req.body)); + break; + } + } + + res.json({ teams }); + } catch (err) { + next(err); + } +}); + +// router.get('/teams', async (req, res, next) => { +// try { +// const teams = await Team.getAllTeamsForUser(req.user.id); + +// console.log(teams); + +// res.json({ teams }); +// } catch (err) { +// next(err); +// } +// }); + +router.get('/teams/get-members', async (req, res, next) => { + try { + const users = await User.getMembersForTeam({ + userId: req.user.id, + teamId: req.query.teamId as string, + }); + + res.json({ users }); + } catch (err) { + next(err); + } +}); + +router.post('/discussions/add', async (req, res, next) => { + try { + const { name, teamId, memberIds = [], socketId, notificationType } = req.body; + + const discussion = await Discussion.add({ + userId: req.user.id, + name, + teamId, + memberIds, + notificationType, + }); + + discussionAdded({ socketId, discussion }); + + res.json({ discussion }); + } catch (err) { + next(err); + } +}); + +router.post('/discussions/edit', async (req, res, next) => { + try { + const { name, id, memberIds = [], socketId, notificationType } = req.body; + + const updatedDiscussion = await Discussion.edit({ + userId: req.user.id, + name, + id, + memberIds, + notificationType, + }); + + discussionEdited({ socketId, discussion: updatedDiscussion }); + + res.json({ done: 1 }); + } catch (err) { + next(err); + } +}); + +router.post('/discussions/delete', async (req, res, next) => { + try { + const { id, socketId } = req.body; + + const { teamId } = await Discussion.delete({ userId: req.user.id, id }); + + discussionDeleted({ socketId, teamId, id }); + + res.json({ done: 1 }); + } catch (err) { + next(err); + } +}); + +router.get('/discussions/list', async (req, res, next) => { + try { + const { discussions } = await Discussion.getList({ + userId: req.user.id, + teamId: req.query.teamId as string, + }); + + res.json({ discussions }); + } catch (err) { + next(err); + } +}); + +router.get('/posts/list', async (req, res, next) => { + try { + const posts = await Post.getList({ + userId: req.user.id, + discussionId: req.query.discussionId as string, + }); + + res.json({ posts }); + } catch (err) { + next(err); + } +}); + +router.post('/posts/add', async (req, res, next) => { + try { + const { content, discussionId, socketId } = req.body; + + const post = await Post.add({ userId: req.user.id, content, discussionId }); + + postAdded({ socketId, post }); + + res.json({ post }); + } catch (err) { + next(err); + } +}); + +router.post('/posts/edit', async (req, res, next) => { + try { + const { content, id, socketId } = req.body; + + const updatedPost = await Post.edit({ userId: req.user.id, content, id }); + + postEdited({ socketId, post: updatedPost }); + + res.json({ done: 1 }); + } catch (err) { + next(err); + } +}); + +router.post('/posts/delete', async (req, res, next) => { + try { + const { id, discussionId, socketId } = req.body; + + await Post.delete({ userId: req.user.id, id }); + + postDeleted({ socketId, id, discussionId }); + + res.json({ done: 1 }); + } catch (err) { + next(err); + } +}); + +export default router; diff --git a/book/11-begin/api/server/aws-s3.ts b/book/11-begin/api/server/aws-s3.ts new file mode 100644 index 00000000..28d938b7 --- /dev/null +++ b/book/11-begin/api/server/aws-s3.ts @@ -0,0 +1,60 @@ +import * as url from 'url'; +import * as aws from 'aws-sdk'; + +async function signRequestForUpload({ + fileName, + fileType, + prefix, + bucket, +}: { + fileName: string; + fileType: string; + prefix: string; + bucket: string; +}) { + const randomStringForPrefix = + Math.random().toString(36).substring(2, 12) + Math.random().toString(36).substring(2, 12); + + const key = `${prefix}/${randomStringForPrefix}/${fileName}`; + + const acl = 'public-read'; + + const params = { + Bucket: bucket, + Key: key, + Expires: 60, + ContentType: fileType, + ACL: acl, + }; + + console.log(key, bucket); + + const s3 = new aws.S3({ + apiVersion: 'latest', + region: process.env.AWS_REGION, + accessKeyId: process.env.AWS_ACCESSKEYID, + secretAccessKey: process.env.AWS_SECRETACCESSKEY, + }); + + return new Promise((resolve, reject) => { + s3.getSignedUrl('putObject', params, (err, data) => { + const parsedUrl = url.parse(data); + + const returnData = { + signedRequest: data, + url: `${parsedUrl.protocol}//${parsedUrl.hostname}${parsedUrl.pathname}`, + }; + + console.log(returnData); + + if (err) { + console.error(err); + reject(err); + } else { + resolve(returnData); + } + }); + }); +} + +export { signRequestForUpload }; diff --git a/book/11-begin/api/server/aws-ses.ts b/book/11-begin/api/server/aws-ses.ts new file mode 100644 index 00000000..fff1565e --- /dev/null +++ b/book/11-begin/api/server/aws-ses.ts @@ -0,0 +1,40 @@ +import * as aws from 'aws-sdk'; + +export default function sendEmail(options) { + const ses = new aws.SES({ + apiVersion: 'latest', + region: process.env.AWS_REGION, + accessKeyId: process.env.AWS_ACCESSKEYID, + secretAccessKey: process.env.AWS_SECRETACCESSKEY, + }); + + return new Promise((resolve, reject) => { + ses.sendEmail( + { + Source: options.from, + Destination: { + CcAddresses: options.cc, + ToAddresses: options.to, + }, + Message: { + Subject: { + Data: options.subject, + }, + Body: { + Html: { + Data: options.body, + }, + }, + }, + ReplyToAddresses: options.replyTo, + }, + (err, info) => { + if (err) { + reject(err); + } else { + resolve(info); + } + }, + ); + }); +} diff --git a/book/11-begin/api/server/google-auth.ts b/book/11-begin/api/server/google-auth.ts new file mode 100644 index 00000000..82e08fe7 --- /dev/null +++ b/book/11-begin/api/server/google-auth.ts @@ -0,0 +1,111 @@ +import * as passport from 'passport'; +import { OAuth2Strategy as Strategy } from 'passport-google-oauth'; + +import User, { UserDocument } from './models/User'; +import Invitation from './models/Invitation'; + +const dev = process.env.NODE_ENV !== 'production'; + +function setupGoogle({ server }) { + if (!process.env.GOOGLE_CLIENTID) { + return; + } + + const verify = async (accessToken, refreshToken, profile, done) => { + let email; + let avatarUrl; + + if (profile.emails) { + email = profile.emails[0].value; + } + + if (profile.photos && profile.photos.length > 0) { + avatarUrl = profile.photos[0].value.replace('sz=50', 'sz=128'); + } + + try { + const user = await User.signInOrSignUpViaGoogle({ + googleId: profile.id, + email, + googleToken: { accessToken, refreshToken }, + displayName: profile.displayName, + avatarUrl, + }); + + done(null, user); + } catch (err) { + done(err); + console.error(err); + } + }; + + passport.use( + new Strategy( + { + clientID: process.env.GOOGLE_CLIENTID, + clientSecret: process.env.GOOGLE_CLIENTSECRET, + callbackURL: `${dev ? process.env.URL_API : process.env.PRODUCTION_URL_API}/oauth2callback`, + }, + verify, + ), + ); + + passport.serializeUser((user: UserDocument, done) => { + done(null, user._id); + }); + + passport.deserializeUser((id, done) => { + User.findById(id, User.publicFields()).exec((err, user) => { + done(err, user); + }); + }); + + server.use(passport.initialize()); + server.use(passport.session()); + + server.get('/auth/google', (req, res, next) => { + const options = { + scope: ['profile', 'email'], + prompt: 'select_account', + }; + + if (req.query && req.query.invitationToken) { + req.session.invitationToken = req.query.invitationToken; + } else { + req.session.invitationToken = null; + } + + passport.authenticate('google', options)(req, res, next); + }); + + server.get( + '/oauth2callback', + passport.authenticate('google', { + failureRedirect: '/login', + }), + (req, res) => { + if (req.user && req.session.invitationToken) { + Invitation.addUserToTeam({ + token: req.session.invitationToken, + user: req.user, + }).catch((err) => console.error(err)); + + req.session.invitationToken = null; + } + + let redirectUrlAfterLogin; + + if (req.user && !req.user.defaultTeamSlug) { + redirectUrlAfterLogin = '/create-team'; + } else { + redirectUrlAfterLogin = `/teams/${req.user.defaultTeamSlug}/discussions`; + } + + res.redirect( + `${dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP}${redirectUrlAfterLogin}`, + ); + }, + ); +} + +export { setupGoogle }; diff --git a/book/11-begin/api/server/logger.ts b/book/11-begin/api/server/logger.ts new file mode 100644 index 00000000..996e653e --- /dev/null +++ b/book/11-begin/api/server/logger.ts @@ -0,0 +1,11 @@ +import * as winston from 'winston'; + +const dev = process.env.NODE_ENV !== 'production'; + +const logger = winston.createLogger({ + format: winston.format.simple(), + level: dev ? 'debug' : 'info', + transports: [new winston.transports.Console()], +}); + +export default logger; diff --git a/book/11-begin/api/server/mailchimp.ts b/book/11-begin/api/server/mailchimp.ts new file mode 100644 index 00000000..7abf8d5e --- /dev/null +++ b/book/11-begin/api/server/mailchimp.ts @@ -0,0 +1,44 @@ +import fetch, { Response } from 'node-fetch'; + +const LIST_IDS = { + signups: process.env.MAILCHIMP_SAAS_ALL_LIST_ID, +}; + +function callAPI({ + path, + method, + data, +}: { + path: string; + method: string; + data: { + email_address: string; + status: string; + }; +}): Promise { + const ROOT_URI = `https://${process.env.MAILCHIMP_REGION}.api.mailchimp.com/3.0`; + + return fetch(`${ROOT_URI}${path}`, { + method, + headers: { + Accept: 'application/json', + Authorization: `Basic ${Buffer.from(`apikey:${process.env.MAILCHIMP_API_KEY}`).toString( + 'base64', + )}`, + }, + body: JSON.stringify(data), + }); +} + +async function addToMailchimp({ email, listName }: { email: string; listName: string }) { + const data = { + email_address: email, + status: 'subscribed', + }; + + const path = `/lists/${LIST_IDS[listName]}/members/`; + + await callAPI({ path, method: 'POST', data }); +} + +export { addToMailchimp }; diff --git a/book/11-begin/api/server/models/Discussion.ts b/book/11-begin/api/server/models/Discussion.ts new file mode 100644 index 00000000..a5d8869f --- /dev/null +++ b/book/11-begin/api/server/models/Discussion.ts @@ -0,0 +1,207 @@ +import { uniq } from 'lodash'; +import * as mongoose from 'mongoose'; + +import { generateNumberSlug } from '../utils/slugify'; +import Team, { TeamDocument } from './Team'; +import Post from './Post'; + +mongoose.set('useFindAndModify', false); + +const mongoSchema = new mongoose.Schema({ + createdUserId: { + type: String, + required: true, + }, + teamId: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + slug: { + type: String, + required: true, + }, + memberIds: [ + { + type: String, + }, + ], + createdAt: { + type: Date, + required: true, + default: Date.now, + }, + notificationType: { + type: String, + enum: ['default', 'email'], + required: true, + default: 'default', + }, +}); + +export interface DiscussionDocument extends mongoose.Document { + createdUserId: string; + teamId: string; + name: string; + slug: string; + memberIds: string[]; + createdAt: Date; + notificationType: string; +} + +interface DiscussionModel extends mongoose.Model { + getList({ + userId, + teamId, + }: { + userId: string; + teamId: string; + }): Promise<{ discussions: DiscussionDocument[] }>; + + add({ + name, + userId, + teamId, + memberIds, + notificationType, + }: { + name: string; + userId: string; + teamId: string; + memberIds: string[]; + notificationType: string; + }): Promise; + + edit({ + userId, + id, + name, + memberIds, + notificationType, + }: { + userId: string; + id: string; + name: string; + memberIds: string[]; + notificationType: string; + }): Promise; + + delete({ userId, id }: { userId: string; id: string }): Promise<{ teamId: string }>; + + checkPermissionAndGetTeam({ + userId, + teamId, + memberIds, + }: { + userId: string; + teamId: string; + memberIds: string[]; + }): Promise; +} + +class DiscussionClass extends mongoose.Model { + public static async getList({ userId, teamId }) { + await this.checkPermissionAndGetTeam({ userId, teamId }); + + const filter: any = { teamId, memberIds: userId }; + + const discussions: any[] = await this.find(filter).setOptions({ lean: true }); + + return { discussions }; + } + + public static async add({ name, userId, teamId, memberIds = [], notificationType }) { + if (!name) { + throw new Error('Bad data'); + } + + await this.checkPermissionAndGetTeam({ userId, teamId, memberIds }); + + const slug = await generateNumberSlug(this, { teamId }); + + return this.create({ + createdUserId: userId, + teamId, + name, + slug, + memberIds: uniq([userId, ...memberIds]), + createdAt: new Date(), + notificationType, + }); + } + + public static async edit({ userId, id, name, memberIds = [], notificationType }) { + if (!id) { + throw new Error('Bad data'); + } + + const discussion = await this.findById(id).select('teamId createdUserId').setOptions({ lean: true }); + + const team = await this.checkPermissionAndGetTeam({ + userId, + teamId: discussion.teamId, + memberIds, + }); + + if (discussion.createdUserId !== userId && team.teamLeaderId !== userId) { + throw new Error('Permission denied. Only author or team leader can edit Discussion.'); + } + + const updatedObj = await this.findOneAndUpdate( + { _id: id }, + { + name, + memberIds: uniq([userId, ...memberIds]), + notificationType, + }, + { runValidators: true, new: true }, + ); + + return updatedObj; + } + + public static async delete({ userId, id }) { + if (!id) { + throw new Error('Bad data'); + } + + const discussion = await this.findById(id).select('teamId').setOptions({ lean: true }); + + await this.checkPermissionAndGetTeam({ userId, teamId: discussion.teamId }); + + await Post.deleteMany({ discussionId: id }); + + await this.deleteOne({ _id: id }); + + return { teamId: discussion.teamId }; + } + + private static async checkPermissionAndGetTeam({ userId, teamId, memberIds = [] }) { + if (!userId || !teamId) { + throw new Error('Bad data'); + } + + const team = await Team.findById(teamId).select('memberIds teamLeaderId').setOptions({ lean: true }); + + if (!team || team.memberIds.indexOf(userId) === -1) { + throw new Error('Team not found'); + } + + for (const id of memberIds) { + if (team.memberIds.indexOf(id) === -1) { + throw new Error('Permission denied'); + } + } + + return team; + } +} + +mongoSchema.loadClass(DiscussionClass); + +const Discussion = mongoose.model('Discussion', mongoSchema); + +export default Discussion; diff --git a/book/11-begin/api/server/models/EmailTemplate.ts b/book/11-begin/api/server/models/EmailTemplate.ts new file mode 100644 index 00000000..db9089d7 --- /dev/null +++ b/book/11-begin/api/server/models/EmailTemplate.ts @@ -0,0 +1,101 @@ +import * as _ from 'lodash'; +import * as mongoose from 'mongoose'; + +interface EmailTemplateDocument extends mongoose.Document { + name: string; + subject: string; + message: string; +} + +const EmailTemplate = mongoose.model( + 'EmailTemplate', + new mongoose.Schema({ + name: { + type: String, + required: true, + unique: true, + }, + subject: { + type: String, + required: true, + }, + message: { + type: String, + required: true, + }, + }), +); + +export async function insertTemplates() { + const templates = [ + { + name: 'welcome', + subject: 'Welcome to SaaS boilerplate by Async', + message: `Welcome <%= userName %>, +

+ Thanks for signing up on our SaaS boilerplate! +

+

+ If you are learning how to build a SaaS web app, check out our 2 books: + Builder Book + and + SaaS Boilerplate. +

+

+ Also check out + Async + , our communication tool for small teams of software developers. +

+ Kelly & Timur, Team Async + `, + }, + { + name: 'login', + subject: 'Login link for saas-app.async-await.com', + message: ` +

Log into your account by clicking on this link: <%= loginURL %>.

`, + }, + { + name: 'invitation', + subject: 'You are invited to join a Team at saas-app.async-await.com', + message: `You've been invited to join <%= teamName%>. +
Click here to accept the invitation: <%= invitationURL%> + `, + }, + { + name: 'newPost', + subject: 'New Post was created in Discussion: <%= discussionName %>', + message: `

New Post in Discussion: "<%= discussionName%>" by <%= authorName%>

+ New Post: "<%= postContent %>" +

---

+

View it at <%= discussionLink %>.

+ `, + }, + ]; + + for (const t of templates) { + const et = await EmailTemplate.findOne({ name: t.name }).setOptions({ lean: true }); + const message = t.message.replace(/\n/g, '').replace(/[ ]+/g, ' ').trim(); + + if (!et) { + EmailTemplate.create(Object.assign({}, t, { message })); + } else if (et.subject !== t.subject || et.message !== message) { + EmailTemplate.updateOne({ _id: et._id }, { $set: { message, subject: t.subject } }).exec(); + } + } +} + +export default async function getEmailTemplate(name: string, params: any) { + await insertTemplates(); + + const et = await EmailTemplate.findOne({ name }).setOptions({ lean: true }); + + if (!et) { + throw new Error('Email Template is not found in database.'); + } + + return { + message: _.template(et.message)(params), + subject: _.template(et.subject)(params), + }; +} diff --git a/book/11-begin/api/server/models/Invitation.ts b/book/11-begin/api/server/models/Invitation.ts new file mode 100644 index 00000000..bd77de44 --- /dev/null +++ b/book/11-begin/api/server/models/Invitation.ts @@ -0,0 +1,234 @@ +import * as mongoose from 'mongoose'; + +import sendEmail from '../aws-ses'; +import getEmailTemplate from './EmailTemplate'; +import Team from './Team'; +import User, { UserDocument } from './User'; + +mongoose.set('useFindAndModify', false); + +const mongoSchema = new mongoose.Schema({ + teamId: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + }, + createdAt: { + type: Date, + required: true, + default: Date.now, + expires: 60 * 60 * 24, // delete doc after 24 hours + }, + token: { + type: String, + required: true, + unique: true, + }, +}); + +mongoSchema.index({ teamId: 1, email: 1 }, { unique: true }); + +interface InvitationDocument extends mongoose.Document { + teamId: string; + email: string; + createdAt: Date; + token: string; +} + +interface InvitationModel extends mongoose.Model { + add({ + userId, + teamId, + email, + }: { + userId: string; + teamId: string; + email: string; + }): InvitationDocument; + + getTeamInvitations({ userId, teamId }: { userId: string; teamId: string }); + getTeamByToken({ token }: { token: string }); + removeIfMemberAdded({ token, userId }: { token: string; userId: string }); + addUserToTeam({ token, user }: { token: string; user: UserDocument }); +} + +function generateToken() { + const gen = () => + Math.random().toString(36).substring(2, 12) + Math.random().toString(36).substring(2, 12); + + return `${gen()}`; +} + +class InvitationClass extends mongoose.Model { + public static async add({ userId, teamId, email }) { + if (!teamId || !email) { + throw new Error('Bad data'); + } + + const team = await Team.findById(teamId).setOptions({ lean: true }); + if (!team || team.teamLeaderId !== userId) { + throw new Error('Team does not exist or you have no permission'); + } + + const registeredUser = await User.findOne({ email }) + .select('defaultTeamSlug') + .setOptions({ lean: true }); + + if (registeredUser) { + if (team.memberIds.includes(registeredUser._id.toString())) { + throw new Error('This user is already Team Member.'); + } else { + await Team.updateOne({ _id: team._id }, { $addToSet: { memberIds: registeredUser._id } }); + + if (registeredUser._id !== team.teamLeaderId && !registeredUser.defaultTeamSlug) { + await User.findByIdAndUpdate(registeredUser._id, { + $set: { defaultTeamSlug: team.slug }, + }); + } + } + } + + let token; + const invitation = await this.findOne({ teamId, email }) + .select('token') + .setOptions({ lean: true }); + + if (invitation) { + token = invitation.token; + } else { + token = generateToken(); + while ((await this.countDocuments({ token })) > 0) { + token = generateToken(); + } + + await this.create({ + teamId, + email, + token, + }); + } + + const dev = process.env.NODE_ENV !== 'production'; + + const emailTemplate = await getEmailTemplate('invitation', { + teamName: team.name, + invitationURL: `${ + dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP + }/invitation?token=${token}`, + }); + + if (!emailTemplate) { + throw new Error('Invitation email template not found'); + } + + try { + await sendEmail({ + from: `Kelly from saas-app.async-await.com <${process.env.EMAIL_SUPPORT_FROM_ADDRESS}>`, + to: [email], + subject: emailTemplate.subject, + body: emailTemplate.message, + }); + } catch (err) { + console.log('Email sending error:', err); + } + + return await this.findOne({ teamId, email }).setOptions({ lean: true }); + } + + public static async getTeamInvitations({ userId, teamId }) { + const team = await Team.findOne({ _id: teamId }) + .select('teamLeaderId') + .setOptions({ lean: true }); + + if (userId !== team.teamLeaderId) { + throw new Error('You have no permission.'); + } + + return this.find({ teamId }).select('email').setOptions({ lean: true }); + } + + public static async getTeamByToken({ token }) { + if (!token) { + throw new Error('Bad data'); + } + + const invitation = await this.findOne({ token }).setOptions({ lean: true }); + + if (!invitation) { + throw new Error('Invitation not found'); + } + + const team = await Team.findById(invitation.teamId) + .select('name slug avatarUrl memberIds') + .setOptions({ lean: true }); + + if (!team) { + throw new Error('Team does not exist'); + } + + return team; + } + + public static async removeIfMemberAdded({ token, userId }) { + if (!token) { + throw new Error('Bad data'); + } + + const invitation = await this.findOne({ token }).setOptions({ lean: true }); + + if (!invitation) { + throw new Error('Invitation not found'); + } + + const team = await Team.findById(invitation.teamId) + .select('name slug avatarUrl memberIds') + .setOptions({ lean: true }); + + if (!team) { + throw new Error('Team does not exist'); + } + + if (team && team.memberIds.includes(userId)) { + this.deleteOne({ token }).exec(); + } + } + + public static async addUserToTeam({ token, user }) { + if (!token || !user) { + throw new Error('Bad data'); + } + + const invitation = await this.findOne({ token }).setOptions({ lean: true }); + + if (!invitation || invitation.email !== user.email) { + throw new Error('Invitation not found'); + } + + await this.deleteOne({ token }); + + const team = await Team.findById(invitation.teamId) + .select('memberIds slug teamLeaderId') + .setOptions({ lean: true }); + + if (!team) { + throw new Error('Team does not exist'); + } + + if (team && !team.memberIds.includes(user._id)) { + await Team.updateOne({ _id: team._id }, { $addToSet: { memberIds: user._id } }); + + if (user._id !== team.teamLeaderId && !user.defaultTeamSlug) { + await User.findByIdAndUpdate(user._id, { $set: { defaultTeamSlug: team.slug } }); + } + } + } +} + +mongoSchema.loadClass(InvitationClass); + +const Invitation = mongoose.model('Invitation', mongoSchema); + +export default Invitation; diff --git a/book/11-begin/api/server/models/Post.ts b/book/11-begin/api/server/models/Post.ts new file mode 100644 index 00000000..44e695e4 --- /dev/null +++ b/book/11-begin/api/server/models/Post.ts @@ -0,0 +1,233 @@ +import * as mongoose from 'mongoose'; + +import * as he from 'he'; +import * as hljs from 'highlight.js'; +import * as marked from 'marked'; + +import Discussion from './Discussion'; +import Team from './Team'; + +mongoose.set('useFindAndModify', false); + +const mongoSchema = new mongoose.Schema({ + createdUserId: { + type: String, + required: true, + }, + discussionId: { + type: String, + required: true, + }, + content: { + type: String, + required: true, + }, + htmlContent: { + type: String, + required: true, + }, + isEdited: { + type: Boolean, + default: false, + }, + lastUpdatedAt: Date, + createdAt: { + type: Date, + required: true, + }, +}); + +function markdownToHtml(content) { + const renderer = new marked.Renderer(); + + renderer.link = (href, title, text) => { + const t = title ? ` title="${title}"` : ''; + + if (text.startsWith('@#')) { + return `${text.replace('@#', '@')} `; + } + + return ` + + ${text} + + `; + }; + + marked.setOptions({ + renderer, + breaks: true, + highlight(code, lang) { + if (!lang) { + return hljs.highlightAuto(code).value; + } + + return hljs.highlight(lang, code).value; + }, + }); + + return marked(he.decode(content)); +} + +export interface PostDocument extends mongoose.Document { + createdUserId: string; + discussionId: string; + content: string; + isEdited: boolean; + lastUpdatedAt: Date; + createdAt: Date; +} + +interface PostModel extends mongoose.Model { + getList({ + userId, + discussionId, + }: { + userId: string; + discussionId: string; + }): Promise; + + add({ + content, + userId, + discussionId, + }: { + content: string; + userId: string; + discussionId: string; + }): Promise; + + edit({ + content, + userId, + id, + }: { + content: string; + userId: string; + id: string; + }): Promise; + + delete({ userId, id }: { userId: string; id: string }): Promise; + + checkPermissionAndGetTeamAndDiscussion({ + userId, + discussionId, + post, + }: { + userId: string; + discussionId: string; + post: PostDocument; + }): Promise<{ TeamDocument; DiscussionDocument }>; +} + +class PostClass extends mongoose.Model { + public static async getList({ userId, discussionId }) { + await this.checkPermissionAndGetTeamAndDiscussion({ userId, discussionId }); + + const filter: any = { discussionId }; + + const posts: any[] = await this.find(filter).sort({ createdAt: 1 }).setOptions({ lean: true }); + + return posts; + } + + public static async add({ content, userId, discussionId }) { + if (!content) { + throw new Error('Bad data'); + } + + await this.checkPermissionAndGetTeamAndDiscussion({ userId, discussionId }); + + const htmlContent = markdownToHtml(content); + + const post = await this.create({ + createdUserId: userId, + discussionId, + content, + htmlContent, + createdAt: new Date(), + }); + + return post; + } + + public static async edit({ content, userId, id }) { + if (!content || !id) { + throw new Error('Bad data'); + } + + const post = await this.findById(id).select('createdUserId discussionId').setOptions({ lean: true }); + + await this.checkPermissionAndGetTeamAndDiscussion({ + userId, + discussionId: post.discussionId, + post, + }); + + const htmlContent = markdownToHtml(content); + + const updatedObj = await this.findOneAndUpdate( + { _id: id }, + { content, htmlContent, isEdited: true, lastUpdatedAt: new Date() }, + { runValidators: true, new: true }, + ); + + return updatedObj; + } + + public static async delete({ userId, id }) { + if (!id) { + throw new Error('Bad data'); + } + + const post = await this.findById(id).select('createdUserId discussionId content').setOptions({ lean: true }); + + await this.checkPermissionAndGetTeamAndDiscussion({ + userId, + discussionId: post.discussionId, + post, + }); + + await this.deleteOne({ _id: id }); + } + + private static async checkPermissionAndGetTeamAndDiscussion({ + userId, + discussionId, + post = null, + }) { + if (!userId || !discussionId) { + throw new Error('Bad data'); + } + + if (post && post.createdUserId !== userId) { + throw new Error('Permission denied'); + } + + const discussion = await Discussion.findById(discussionId) + .select('teamId memberIds slug') + .setOptions({ lean: true }); + + if (!discussion) { + throw new Error('Discussion not found'); + } + + if (discussion.memberIds.indexOf(userId) === -1) { + throw new Error('Permission denied'); + } + + const team = await Team.findById(discussion.teamId).select('memberIds slug').setOptions({ lean: true }); + + if (!team || team.memberIds.indexOf(userId) === -1) { + throw new Error('Team not found'); + } + + return { team, discussion }; + } +} + +mongoSchema.loadClass(PostClass); + +const Post = mongoose.model('Post', mongoSchema); + +export default Post; diff --git a/book/11-begin/api/server/models/Team.ts b/book/11-begin/api/server/models/Team.ts new file mode 100644 index 00000000..047cc105 --- /dev/null +++ b/book/11-begin/api/server/models/Team.ts @@ -0,0 +1,298 @@ +import * as mongoose from 'mongoose'; +import Stripe from 'stripe'; + +import { cancelSubscription } from '../stripe'; +import { generateNumberSlug } from '../utils/slugify'; +import User from './User'; + +mongoose.set('useFindAndModify', false); + +const mongoSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + }, + slug: { + type: String, + required: true, + unique: true, + }, + avatarUrl: String, + createdAt: { + type: Date, + required: true, + }, + teamLeaderId: { + type: String, + required: true, + }, + memberIds: [ + { + type: String, + required: true, + }, + ], + defaultTeam: { + type: Boolean, + default: false, + }, + stripeSubscription: { + id: String, + object: String, + application_fee_percent: Number, + billing: String, + cancel_at_period_end: Boolean, + billing_cycle_anchor: Number, + canceled_at: Number, + created: Number, + }, + isSubscriptionActive: { + type: Boolean, + default: false, + }, + isPaymentFailed: { + type: Boolean, + default: false, + }, +}); + +export interface TeamDocument extends mongoose.Document { + name: string; + slug: string; + avatarUrl: string; + createdAt: Date; + + teamLeaderId: string; + memberIds: string[]; + defaultTeam: boolean; + + stripeSubscription: { + id: string; + object: string; + application_fee_percent: number; + billing: string; + cancel_at_period_end: boolean; + billing_cycle_anchor: number; + canceled_at: number; + created: number; + }; + isSubscriptionActive: boolean; + isPaymentFailed: boolean; +} + +interface TeamModel extends mongoose.Model { + addTeam({ + name, + userId, + }: { + userId: string; + name: string; + avatarUrl: string; + }): Promise; + + updateTeam({ + userId, + teamId, + name, + avatarUrl, + }: { + userId: string; + teamId: string; + name: string; + avatarUrl: string; + }): Promise; + + getAllTeamsForUser(userId: string): Promise; + + removeMember({ + teamId, + teamLeaderId, + userId, + }: { + teamId: string; + teamLeaderId: string; + userId: string; + }): Promise; + + subscribeTeam({ + session, + team, + }: { + session: Stripe.Checkout.Session; + team: TeamDocument; + }): Promise; + + cancelSubscription({ + teamLeaderId, + teamId, + }: { + teamLeaderId: string; + teamId: string; + }): Promise; + + cancelSubscriptionAfterFailedPayment({ + subscriptionId, + }: { + subscriptionId: string; + }): Promise; +} + +class TeamClass extends mongoose.Model { + public static async addTeam({ userId, name, avatarUrl }) { + console.log(`Static method: ${name}, ${avatarUrl}`); + + if (!userId || !name || !avatarUrl) { + throw new Error('Bad data'); + } + + const slug = await generateNumberSlug(this); + + let defaultTeam = false; + if ((await this.countDocuments({ teamLeaderId: userId })) === 0) { + await User.findByIdAndUpdate(userId, { $set: { defaultTeamSlug: slug } }); + defaultTeam = true; + } + + const team = await this.create({ + teamLeaderId: userId, + name, + slug, + avatarUrl, + memberIds: [userId], + createdAt: new Date(), + defaultTeam, + }); + + return team; + } + + public static async updateTeam({ userId, teamId, name, avatarUrl }) { + const team = await this.findById(teamId, 'name teamLeaderId'); + + if (!team) { + throw new Error('Team not found'); + } + + if (team.teamLeaderId !== userId) { + throw new Error('Permission denied'); + } + + const modifier = { name: team.name, avatarUrl }; + + if (name !== team.name) { + modifier.name = name; + } + + await this.updateOne({ _id: teamId }, { $set: modifier }, { runValidators: true }); + + return this.findById(teamId, 'name avatarUrl slug defaultTeam').setOptions({ lean: true }); + } + + public static getAllTeamsForUser(userId: string) { + console.log(`userId:${userId}`); + return this.find({ memberIds: userId }).setOptions({ lean: true }); + } + + public static async removeMember({ teamId, teamLeaderId, userId }) { + const team = await this.findById(teamId).select('memberIds teamLeaderId'); + + if (!team) { + throw new Error('Team does not exist'); + } + + if (team.teamLeaderId !== teamLeaderId || teamLeaderId === userId) { + throw new Error('Permission denied'); + } + + // @ts-expect-error probably problem with @types/mongoose, works with $set but not $pull + await this.findByIdAndUpdate(teamId, { $pull: { memberIds: userId } }); + } + + public static async subscribeTeam({ + session, + team, + }: { + session: Stripe.Checkout.Session; + team: TeamDocument; + }) { + if (!session.subscription) { + throw new Error('Not subscribed'); + } + + if (!team) { + throw new Error('User not found.'); + } + + if (team.isSubscriptionActive) { + throw new Error('Team is already subscribed.'); + } + + const stripeSubscription = session.subscription as Stripe.Subscription; + if (stripeSubscription.canceled_at) { + throw new Error('Unsubscribed'); + } + + await this.updateOne({ _id: team._id }, { stripeSubscription, isSubscriptionActive: true }); + } + + public static async cancelSubscription({ teamLeaderId, teamId }) { + const team = await this.findById(teamId).select( + 'teamLeaderId isSubscriptionActive stripeSubscription', + ); + + if (team.teamLeaderId !== teamLeaderId) { + throw new Error('You do not have permission to subscribe Team.'); + } + + if (!team.isSubscriptionActive) { + throw new Error('Team is already unsubscribed.'); + } + + const cancelledSubscriptionObj = await cancelSubscription({ + subscriptionId: team.stripeSubscription.id, + }); + + return this.findByIdAndUpdate( + teamId, + { + stripeSubscription: cancelledSubscriptionObj, + isSubscriptionActive: false, + }, + { new: true, runValidators: true }, + ) + .select('isSubscriptionActive stripeSubscription') + .setOptions({ lean: true }); + } + + public static async cancelSubscriptionAfterFailedPayment({ subscriptionId }) { + const team: any = await this.find({ 'stripeSubscription.id': subscriptionId }) + .select('teamLeaderId isSubscriptionActive stripeSubscription isPaymentFailed') + .setOptions({ lean: true }); + if (!team.isSubscriptionActive) { + throw new Error('Team is already unsubscribed.'); + } + if (team.isPaymentFailed) { + throw new Error('Team is already unsubscribed after failed payment.'); + } + const cancelledSubscriptionObj = await cancelSubscription({ + subscriptionId, + }); + return this.findByIdAndUpdate( + team._id, + { + stripeSubscription: cancelledSubscriptionObj, + isSubscriptionActive: false, + isPaymentFailed: true, + }, + { new: true, runValidators: true }, + ) + .select('isSubscriptionActive stripeSubscription isPaymentFailed') + .setOptions({ lean: true }); + } +} + +mongoSchema.loadClass(TeamClass); + +const Team = mongoose.model('Team', mongoSchema); + +export default Team; diff --git a/book/11-begin/api/server/models/User.ts b/book/11-begin/api/server/models/User.ts new file mode 100644 index 00000000..61129f7e --- /dev/null +++ b/book/11-begin/api/server/models/User.ts @@ -0,0 +1,484 @@ +import * as _ from 'lodash'; +import * as mongoose from 'mongoose'; +import Stripe from 'stripe'; + +import sendEmail from '../aws-ses'; +import { addToMailchimp } from '../mailchimp'; +import { generateSlug } from '../utils/slugify'; +import getEmailTemplate from './EmailTemplate'; +import Team, { TeamDocument } from './Team'; + +import { getListOfInvoices } from '../stripe'; + +mongoose.set('useFindAndModify', false); + +const mongoSchema = new mongoose.Schema({ + slug: { + type: String, + required: true, + unique: true, + }, + createdAt: { + type: Date, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + displayName: String, + avatarUrl: String, + googleId: { + type: String, + unique: true, + sparse: true, + }, + googleToken: { + accessToken: String, + refreshToken: String, + }, + isSignedupViaGoogle: { + type: Boolean, + required: true, + default: false, + }, + darkTheme: Boolean, + defaultTeamSlug: { + type: String, + default: '', + }, + stripeCustomer: { + id: String, + object: String, + created: Number, + currency: String, + default_source: String, + description: String, + }, + stripeCard: { + id: String, + object: String, + brand: String, + funding: String, + country: String, + last4: String, + exp_month: Number, + exp_year: Number, + }, + hasCardInformation: { + type: Boolean, + default: false, + }, + stripeListOfInvoices: { + object: String, + has_more: Boolean, + data: [ + { + id: String, + object: String, + amount_paid: Number, + created: Number, + customer: String, + subscription: String, + hosted_invoice_url: String, + billing: String, + paid: Boolean, + number: String, + teamId: String, + teamName: String, + }, + ], + }, +}); + +export interface UserDocument extends mongoose.Document { + slug: string; + createdAt: Date; + email: string; + displayName: string; + avatarUrl: string; + googleId: string; + googleToken: { accessToken: string; refreshToken: string }; + isSignedupViaGoogle: boolean; + darkTheme: boolean; + defaultTeamSlug: string; + stripeCustomer: { + id: string; + default_source: string; + created: number; + object: string; + description: string; + }; + stripeCard: { + id: string; + object: string; + brand: string; + country: string; + last4: string; + exp_month: number; + exp_year: number; + funding: string; + }; + hasCardInformation: boolean; + stripeListOfInvoices: { + object: string; + has_more: boolean; + data: [ + { + id: string; + object: string; + amount_paid: number; + date: number; + customer: string; + subscription: string; + hosted_invoice_url: string; + billing: string; + paid: boolean; + number: string; + teamId: string; + teamName: string; + }, + ]; + }; +} + +interface UserModel extends mongoose.Model { + getUserBySlug({ slug }: { slug: string }): Promise; + + updateProfile({ + userId, + name, + avatarUrl, + }: { + userId: string; + name: string; + avatarUrl: string; + }): Promise; + + publicFields(): string[]; + + signInOrSignUpViaGoogle({ + googleId, + email, + displayName, + avatarUrl, + googleToken, + }: { + googleId: string; + email: string; + displayName: string; + avatarUrl: string; + googleToken: { accessToken?: string; refreshToken?: string }; + }): Promise; + + signInOrSignUpByPasswordless({ + uid, + email, + }: { + uid: string; + email: string; + }): Promise; + + toggleTheme({ userId, darkTheme }: { userId: string; darkTheme: boolean }): Promise; + + getMembersForTeam({ + userId, + teamId, + }: { + userId: string; + teamId: string; + }): Promise; + + checkPermissionAndGetTeam({ + userId, + teamId, + }: { + userId: string; + teamId: string; + }): Promise; + + saveStripeCustomerAndCard({ + user, + session, + }: { + session: Stripe.Checkout.Session; + user: UserDocument; + }): Promise; + + changeStripeCard({ + session, + user, + }: { + session: Stripe.Checkout.Session; + user: UserDocument; + }): Promise; + + getListOfInvoicesForCustomer({ userId }: { userId: string }): Promise; +} + +class UserClass extends mongoose.Model { + public static async getUserBySlug({ slug }) { + console.log('Static method: getUserBySlug'); + + return this.findOne({ slug }, 'email displayName avatarUrl'); + } + + public static async updateProfile({ userId, name, avatarUrl }) { + console.log('Static method: updateProfile'); + + const user = await this.findById(userId, 'slug displayName'); + + const modifier = { displayName: user.displayName, avatarUrl, slug: user.slug }; + + console.log(user.slug); + + if (name !== user.displayName) { + modifier.displayName = name; + modifier.slug = await generateSlug(this, name); + } + + return this.findByIdAndUpdate(userId, { $set: modifier }, { new: true, runValidators: true }) + .select('displayName avatarUrl slug') + .setOptions({ lean: true }); + } + + public static publicFields(): string[] { + return [ + '_id', + 'id', + 'displayName', + 'email', + 'avatarUrl', + 'slug', + 'isSignedupViaGoogle', + 'darkTheme', + 'defaultTeamSlug', + 'stripeCard', + 'hasCardInformation', + 'stripeListOfInvoices', + ]; + } + + public static async signInOrSignUpViaGoogle({ + googleId, + email, + displayName, + avatarUrl, + googleToken, + }) { + const user = await this.findOne({ email }) + .select([...this.publicFields(), 'googleId'].join(' ')) + .setOptions({ lean: true }); + + if (user) { + if (_.isEmpty(googleToken) && user.googleId) { + return user; + } + + const modifier = { googleId }; + if (googleToken.accessToken) { + modifier['googleToken.accessToken'] = googleToken.accessToken; + } + + if (googleToken.refreshToken) { + modifier['googleToken.refreshToken'] = googleToken.refreshToken; + } + + await this.updateOne({ email }, { $set: modifier }); + + return user; + } + + const slug = await generateSlug(this, displayName); + + const newUser = await this.create({ + createdAt: new Date(), + googleId, + email, + googleToken, + displayName, + avatarUrl, + slug, + isSignedupViaGoogle: true, + defaultTeamSlug: '', + }); + + const emailTemplate = await getEmailTemplate('welcome', { userName: displayName }); + + if (!emailTemplate) { + throw new Error('Welcome email template not found'); + } + + try { + await sendEmail({ + from: `Kelly from saas-app.async-await.com <${process.env.EMAIL_SUPPORT_FROM_ADDRESS}>`, + to: [email], + subject: emailTemplate.subject, + body: emailTemplate.message, + }); + } catch (err) { + console.error('Email sending error:', err); + } + + try { + await addToMailchimp({ email, listName: 'signups' }); + } catch (error) { + console.error('Mailchimp error:', error); + } + + return _.pick(newUser, this.publicFields()); + } + + public static async signInOrSignUpByPasswordless({ uid, email }) { + const user = await this.findOne({ email }).select(this.publicFields().join(' ')).setOptions({ lean: true }); + + if (user) { + throw Error('User already exists'); + } + + const slug = await generateSlug(this, email); + + const newUser = await this.create({ + _id: uid, + createdAt: new Date(), + email, + slug, + defaultTeamSlug: '', + }); + + const emailTemplate = await getEmailTemplate('welcome', { userName: email }); + + if (!emailTemplate) { + throw new Error('Email template "welcome" not found in database.'); + } + + try { + await sendEmail({ + from: `Kelly from saas-app.async-await.com <${process.env.EMAIL_SUPPORT_FROM_ADDRESS}>`, + to: [email], + subject: emailTemplate.subject, + body: emailTemplate.message, + }); + } catch (err) { + console.error('Email sending error:', err); + } + + try { + await addToMailchimp({ email, listName: 'signups' }); + } catch (error) { + console.error('Mailchimp error:', error); + } + + return _.pick(newUser, this.publicFields()); + } + + // try private instead of public, run `yarn build` + public static toggleTheme({ userId, darkTheme }) { + return this.updateOne({ _id: userId }, { darkTheme: !!darkTheme }); + } + + public static async getMembersForTeam({ userId, teamId }) { + const team = await this.checkPermissionAndGetTeam({ userId, teamId }); + + return this.find({ _id: { $in: team.memberIds } }) + .select(this.publicFields().join(' ')) + .setOptions({ lean: true }); + } + + public static async saveStripeCustomerAndCard({ + user, + session, + }: { + session: Stripe.Checkout.Session; + user: UserDocument; + }) { + if (!user) { + throw new Error('User not found.'); + } + + const stripeSubscription = session.subscription as Stripe.Subscription; + + const stripeCard = + (stripeSubscription.default_payment_method && + (stripeSubscription.default_payment_method as Stripe.PaymentMethod).card) || + undefined; + + const hasCardInformation = !!stripeCard; + + await this.updateOne( + { _id: user._id }, + { + stripeCustomer: session.customer, + stripeCard, + hasCardInformation, + }, + ); + } + + public static async changeStripeCard({ + session, + user, + }: { + session: Stripe.Checkout.Session; + user: UserDocument; + }): Promise { + if (!user) { + throw new Error('User not found.'); + } + + const si: Stripe.SetupIntent = session.setup_intent as Stripe.SetupIntent; + const pm: Stripe.PaymentMethod = si.payment_method as Stripe.PaymentMethod; + + if (!pm.card) { + throw new Error('No card found.'); + } + await this.updateOne({ _id: user._id }, { stripeCard: pm.card, hasCardInformation: true }); + } + + public static async getListOfInvoicesForCustomer({ userId }) { + const user = await this.findById(userId, 'stripeCustomer'); + + if (!user.stripeCustomer.id) { + throw new Error('You are not a customer and you have no payment history.'); + } + + const newListOfInvoices = await getListOfInvoices({ + customerId: user.stripeCustomer.id, + }); + + if (newListOfInvoices.data === undefined || newListOfInvoices.data.length === 0) { + throw new Error('You are a customer. But there is no payment history.'); + } + + const modifier = { + stripeListOfInvoices: newListOfInvoices, + }; + + return this.findByIdAndUpdate(userId, { $set: modifier }, { new: true, runValidators: true }) + .select('stripeListOfInvoices') + .setOptions({ lean: true }); + } + + private static async checkPermissionAndGetTeam({ userId, teamId }) { + console.log(userId, teamId); + + if (!userId || !teamId) { + throw new Error('Bad data'); + } + + const team = await Team.findById(teamId).select('memberIds').setOptions({ lean: true }); + + if (!team || team.memberIds.indexOf(userId) === -1) { + throw new Error('Team not found'); + } + + return team; + } +} + +mongoSchema.loadClass(UserClass); + +const User = mongoose.model('User', mongoSchema); + +export default User; diff --git a/book/11-begin/api/server/passwordless-auth.ts b/book/11-begin/api/server/passwordless-auth.ts new file mode 100644 index 00000000..db3748b9 --- /dev/null +++ b/book/11-begin/api/server/passwordless-auth.ts @@ -0,0 +1,121 @@ +import * as passwordless from 'passwordless'; + +import sendEmail from './aws-ses'; +import getEmailTemplate from './models/EmailTemplate'; +import User from './models/User'; +import PasswordlessMongoStore from './passwordless-token-mongostore'; +import Invitation from './models/Invitation'; + +function setupPasswordless({ server }) { + const mongoStore = new PasswordlessMongoStore(); + + const dev = process.env.NODE_ENV !== 'production'; + + passwordless.addDelivery(async (tokenToSend, uidToSend, recipient, callback) => { + try { + const template = await getEmailTemplate('login', { + loginURL: `${ + dev ? process.env.URL_API : process.env.PRODUCTION_URL_API + }/auth/logged_in?token=${tokenToSend}&uid=${encodeURIComponent(uidToSend)}`, + }); + + await sendEmail({ + from: `Kelly from saas-app.async-await.com <${process.env.EMAIL_SUPPORT_FROM_ADDRESS}>`, + to: [recipient], + subject: template.subject, + body: template.message, + }); + + callback(); + } catch (err) { + console.error('Email sending error:', err); + callback(err); + } + }); + + passwordless.init(mongoStore); + server.use(passwordless.sessionSupport()); + + server.use((req, __, next) => { + if (req.user && typeof req.user === 'string') { + User.findById(req.user, User.publicFields()).exec((err, user) => { + req.user = user; + console.log('passwordless middleware'); + next(err); + }); + } else { + next(); + } + }); + + server.post( + '/auth/email-login-link', + passwordless.requestToken(async (email, __, callback) => { + try { + const user = await User.findOne({ email }).select('_id').setOptions({ lean: true }); + + if (user) { + callback(null, user._id); + } else { + const id = await mongoStore.storeOrUpdateByEmail(email); + callback(null, id); + } + } catch (error) { + callback(error, null); + } + }), + (req, res) => { + if (req.query && req.query.invitationToken) { + req.session.invitationToken = req.query.invitationToken; + } else { + req.session.invitationToken = null; + } + + res.json({ done: 1 }); + }, + ); + + server.get( + '/auth/logged_in', + passwordless.acceptToken(), + (req, __, next) => { + if (req.user && typeof req.user === 'string') { + User.findById(req.user, User.publicFields()).exec((err, user) => { + req.user = user; + next(err); + }); + } else { + next(); + } + }, + (req, res) => { + if (req.user && req.session.invitationToken) { + Invitation.addUserToTeam({ + token: req.session.invitationToken, + user: req.user, + }).catch((err) => console.error(err)); + + req.session.invitationToken = null; + } + + let redirectUrlAfterLogin; + + if (req.user && !req.user.defaultTeamSlug) { + redirectUrlAfterLogin = '/create-team'; + } else { + redirectUrlAfterLogin = `/teams/${req.user.defaultTeamSlug}/discussions`; + } + + res.redirect( + `${dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP}${redirectUrlAfterLogin}`, + ); + }, + ); + + server.get('/logout', passwordless.logout(), (req, res) => { + req.logout(); + res.redirect(`${dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP}/login`); + }); +} + +export { setupPasswordless }; diff --git a/book/11-begin/api/server/passwordless-token-mongostore.ts b/book/11-begin/api/server/passwordless-token-mongostore.ts new file mode 100644 index 00000000..7ebc9c11 --- /dev/null +++ b/book/11-begin/api/server/passwordless-token-mongostore.ts @@ -0,0 +1,147 @@ +import * as bcrypt from 'bcrypt'; +import * as mongoose from 'mongoose'; +import * as TokenStore from 'passwordless-tokenstore'; +import * as util from 'util'; + +import User from './models/User'; + +interface TokenDocument extends mongoose.Document { + hashedToken: string; + uid: string; + ttl: Date; + originUrl: string; + email: string; +} + +const mongoSchema = new mongoose.Schema({ + hashedToken: { + type: String, + required: true, + }, + uid: { + type: String, + required: true, + unique: true, + }, + ttl: { + type: Date, + required: true, + expires: 0, + }, + originUrl: String, + email: String, +}); + +const PasswordlessToken = mongoose.model( + 'PasswordlessToken', + mongoSchema, + 'passwordless-token', +); + +function MongoStore(options = {}) { + TokenStore.call(this); + + this._options = options || {}; +} + +util.inherits(MongoStore, TokenStore); + +MongoStore.prototype.authenticate = async function (token, uid, callback) { + if (!token || !uid || !callback) { + throw new Error('TokenStore:authenticate called with invalid parameters'); + } + + try { + const tokenDoc = await PasswordlessToken.findOne({ uid, ttl: { $gt: new Date() } }).setOptions({ + lean: true, + }); + + if (tokenDoc) { + const isMatch = await bcrypt.compare(token, tokenDoc.hashedToken); + if (isMatch) { + if (tokenDoc.email) { + await User.signInOrSignUpByPasswordless({ uid, email: tokenDoc.email }); + } + + callback(null, true, tokenDoc.originUrl); + } else { + callback(null, false, null); + } + } else { + callback(null, false, null); + } + } catch (error) { + callback(error, false, null); + } +}; + +MongoStore.prototype.storeOrUpdate = async function (token, uid, msToLive, originUrl, callback) { + if (!token || !uid || !msToLive || !callback) { + throw new Error('TokenStore:storeOrUpdate called with invalid parameters'); + } + + const saltRounds = 10; + + try { + const hashedToken = await bcrypt.hash(token, saltRounds); + const newRecord = { hashedToken, uid, ttl: new Date(Date.now() + msToLive), originUrl }; + + await PasswordlessToken.updateOne( + { uid }, + { $set: newRecord }, + { upsert: true, runValidators: true }, + ); + callback(); + } catch (error) { + callback(error); + } +}; + +MongoStore.prototype.invalidateUser = async function (uid, callback) { + if (!uid || !callback) { + throw new Error('TokenStore:invalidateUser called with invalid parameters'); + } + + try { + await PasswordlessToken.deleteOne({ uid }); + callback(); + } catch (error) { + callback(error); + } +}; + +MongoStore.prototype.clear = async function (callback) { + if (!callback) { + throw new Error('TokenStore:clear called with invalid parameters'); + } + + try { + await PasswordlessToken.deleteMany({}); + callback(); + } catch (error) { + callback(error); + } +}; + +MongoStore.prototype.length = function (callback) { + PasswordlessToken.countDocuments(callback); +}; + +MongoStore.prototype.storeOrUpdateByEmail = async function addEmail(email: string) { + if (!email) { + throw new Error('TokenStore:addEmail called with invalid parameters'); + } + + const obj = await PasswordlessToken.findOne({ email }).select('uid').setOptions({ lean: true }); + + if (obj) { + return obj.uid; + } + + const uid = mongoose.Types.ObjectId().toHexString(); + await PasswordlessToken.updateOne({ uid }, { email }, { upsert: true }); + + return uid; +}; + +export default MongoStore; diff --git a/book/11-begin/api/server/server.ts b/book/11-begin/api/server/server.ts new file mode 100644 index 00000000..c5a90b50 --- /dev/null +++ b/book/11-begin/api/server/server.ts @@ -0,0 +1,98 @@ +import * as mongoSessionStore from 'connect-mongo'; +import * as cors from 'cors'; +import * as express from 'express'; +import * as session from 'express-session'; +import * as httpModule from 'http'; +import * as mongoose from 'mongoose'; + +import api from './api'; +import { setupGoogle } from './google-auth'; +import { setupPasswordless } from './passwordless-auth'; +import { setupSockets } from './sockets'; +import { stripeWebhookAndCheckoutCallback } from './stripe'; + +import logger from './logger'; + +import * as compression from 'compression'; +import * as helmet from 'helmet'; + +// eslint-disable-next-line +require('dotenv').config(); + +const dev = process.env.NODE_ENV !== 'production'; +const port = process.env.PORT || 8000; + +const options = { + useNewUrlParser: true, + useCreateIndex: true, + useFindAndModify: false, + useUnifiedTopology: true, +}; + +mongoose.connect(dev ? process.env.MONGO_URL_TEST : process.env.MONGO_URL, options); + +const server = express(); + +server.use( + cors({ + origin: dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP, + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + credentials: true, + }), +); + +server.use(helmet()); +server.use(compression()); + +stripeWebhookAndCheckoutCallback({ server }); + +server.use(express.json()); + +const MongoStore = mongoSessionStore(session); + +const sessionOptions = { + name: process.env.SESSION_NAME, + secret: process.env.SESSION_SECRET, + store: new MongoStore({ + mongooseConnection: mongoose.connection, + ttl: 14 * 24 * 60 * 60, // save session 14 days + autoRemove: 'interval', + autoRemoveInterval: 1440, // clears every day + }), + resave: false, + saveUninitialized: false, + cookie: { + httpOnly: true, + maxAge: 14 * 24 * 60 * 60 * 1000, // expires in 14 days + domain: dev ? 'localhost' : process.env.COOKIE_DOMAIN, + } as any, +}; + +if (!dev) { + server.set('trust proxy', 1); // sets req.hostname, req.ip + sessionOptions.cookie.secure = true; // sets cookie over HTTPS only +} + +const sessionMiddleware = session(sessionOptions); +server.use(sessionMiddleware); + +setupGoogle({ server }); +setupPasswordless({ server }); + +api(server); + +const httpServer = httpModule.createServer(server); +setupSockets({ + httpServer, + origin: dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP, + sessionMiddleware, +}); + +server.get('*', (_, res) => { + res.sendStatus(403); +}); + +httpServer.listen(port, () => { + logger.debug('debug right before info'); + logger.info(`> Ready on ${dev ? process.env.URL_API : process.env.PRODUCTION_URL_API}`); +}); diff --git a/book/11-begin/api/server/sockets.ts b/book/11-begin/api/server/sockets.ts new file mode 100644 index 00000000..50e21eee --- /dev/null +++ b/book/11-begin/api/server/sockets.ts @@ -0,0 +1,185 @@ +import { Response } from 'express'; +import * as express from 'express'; +import { Server } from 'socket.io'; +import * as httpModule from 'http'; + +import { DiscussionDocument } from './models/Discussion'; +import { PostDocument } from './models/Post'; + +let io: Server = null; + +const dev = process.env.NODE_ENV !== 'production'; + +function setupSockets({ + httpServer, + origin, + sessionMiddleware, +}: { + httpServer: httpModule.Server; + origin: string | boolean | RegExp | (string | RegExp)[]; + sessionMiddleware: express.RequestHandler; +}) { + if (io === null) { + io = new Server(httpServer, { + cors: { + origin, + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + credentials: true, + }, + cookie: { + httpOnly: true, + maxAge: 14 * 24 * 60 * 60 * 1000, // expires in 14 days + domain: dev ? 'localhost' : '.async-await.com', + secure: dev ? false : true, + }, + serveClient: false, + transports: ['polling', 'websocket'], + }); + + const wrap = (middleware) => (socket, next) => middleware(socket.request, {} as Response, next); + + io.use(wrap(sessionMiddleware)); + + io.on('connection', (socket) => { + if ( + !socket.request.session || + ((!socket.request.session.passport || !socket.request.session.passport.user) && + !socket.request.session.passwordless) + ) { + socket.disconnect(true); + return; + } + + socket.on('joinTeamRoom', (teamId) => { + console.log(` joinTeamRoom ${teamId}`); + socket.join(`teamRoom-${teamId}`); + }); + + socket.on('leaveTeamRoom', (teamId) => { + console.log(`** leaveTeamRoom ${teamId}`); + socket.leave(`teamRoom-${teamId}`); + }); + + socket.on('joinDiscussionRoom', (discussionId) => { + console.log(` joinDiscussionRoom ${discussionId}`); + socket.join(`discussionRoom-${discussionId}`); + }); + + socket.on('leaveDiscussionRoom', (discussionId) => { + console.log(`** leaveDiscussionRoom ${discussionId}`); + socket.leave(`discussionRoom-${discussionId}`); + }); + + socket.on('disconnect', (reason) => { + console.log(`disconnected`, `reason: ` + reason); + }); + }); + } +} + +function getSocket(socketId?: string) { + if (!io) { + return null; + } + + if (socketId && io.sockets.sockets.get(socketId)) { + return io.sockets.sockets.get(socketId).broadcast; + } else { + return io; + } +} + +function discussionAdded({ + socketId, + discussion, +}: { + socketId?: string; + discussion: DiscussionDocument; +}) { + const roomName = `teamRoom-${discussion.teamId}`; + + const socket = getSocket(socketId); + if (socket) { + socket.to(roomName).emit('discussionEvent', { actionType: 'added', discussion }); + } +} + +function discussionEdited({ + socketId, + discussion, +}: { + socketId?: string; + discussion: DiscussionDocument; +}) { + const roomName = `teamRoom-${discussion.teamId}`; + const socket = getSocket(socketId); + + if (socket) { + socket.to(roomName).emit('discussionEvent', { + actionType: 'edited', + discussion, + }); + } +} + +function discussionDeleted({ + socketId, + teamId, + id, +}: { + socketId?: string; + teamId: string; + id: string; +}) { + const roomName = `teamRoom-${teamId}`; + const socket = getSocket(socketId); + + if (socket) { + socket.to(roomName).emit('discussionEvent', { actionType: 'deleted', id }); + } +} + +function postAdded({ socketId, post }: { socketId?: string; post: PostDocument }) { + const roomName = `discussionRoom-${post.discussionId}`; + + const socket = getSocket(socketId); + if (socket) { + socket.to(roomName).emit('postEvent', { actionType: 'added', post }); + } +} + +function postEdited({ socketId, post }: { socketId?: string; post: PostDocument }) { + const roomName = `discussionRoom-${post.discussionId}`; + + const socket = getSocket(socketId); + if (socket) { + socket.to(roomName).emit('postEvent', { actionType: 'edited', post }); + } +} + +function postDeleted({ + socketId, + id, + discussionId, +}: { + socketId?: string; + id: string; + discussionId: string; +}) { + const roomName = `discussionRoom-${discussionId}`; + + const socket = getSocket(socketId); + if (socket) { + socket.to(roomName).emit('postEvent', { actionType: 'deleted', id }); + } +} + +export { + setupSockets, + postAdded, + postEdited, + postDeleted, + discussionAdded, + discussionEdited, + discussionDeleted, +}; diff --git a/book/11-begin/api/server/stripe.ts b/book/11-begin/api/server/stripe.ts new file mode 100644 index 00000000..87881e85 --- /dev/null +++ b/book/11-begin/api/server/stripe.ts @@ -0,0 +1,205 @@ +import * as express from 'express'; +import * as bodyParser from 'body-parser'; +import Stripe from 'stripe'; + +import Team from './models/Team'; +import User from './models/User'; + +import logger from './logger'; + +const dev = process.env.NODE_ENV !== 'production'; + +const stripeInstance = new Stripe( + dev ? process.env.STRIPE_TEST_SECRETKEY : process.env.STRIPE_LIVE_SECRETKEY, + { apiVersion: '2020-08-27' }, +); + +function createSession({ + userId, + teamId, + teamSlug, + customerId, + subscriptionId, + userEmail, + mode, +}: { + userId: string; + teamId: string; + teamSlug: string; + customerId: string; + subscriptionId: string; + userEmail: string; + mode: Stripe.Checkout.SessionCreateParams.Mode; +}) { + const params: Stripe.Checkout.SessionCreateParams = { + customer_email: customerId ? undefined : userEmail, + customer: customerId, + payment_method_types: ['card'], + mode, + success_url: `${ + dev ? process.env.URL_API : process.env.PRODUCTION_URL_API + }/stripe/checkout-completed/{CHECKOUT_SESSION_ID}`, + cancel_url: `${ + dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP + }/teams/${teamSlug}/billing?redirectMessage=Checkout%20canceled`, + metadata: { userId, teamId }, + }; + + if (mode === 'subscription') { + params.line_items = [ + { + price: dev ? process.env.STRIPE_TEST_PRICEID : process.env.STRIPE_LIVE_PRICEID, + quantity: 1, + }, + ]; + } else if (mode === 'setup') { + if (!customerId || !subscriptionId) { + throw new Error('customerId and subscriptionId required'); + } + + params.setup_intent_data = { + metadata: { customer_id: customerId, subscription_id: subscriptionId }, + }; + } + + return stripeInstance.checkout.sessions.create(params); +} + +function retrieveSession({ sessionId }: { sessionId: string }) { + return stripeInstance.checkout.sessions.retrieve(sessionId, { + expand: [ + 'setup_intent', + 'setup_intent.payment_method', + 'customer', + 'subscription', + 'subscription.default_payment_method', + ], + }); +} + +function updateCustomer(customerId, params: Stripe.CustomerUpdateParams) { + logger.debug('updating customer', customerId); + return stripeInstance.customers.update(customerId, params); +} + +function updateSubscription(subscriptionId: string, params: Stripe.SubscriptionUpdateParams) { + logger.debug('updating subscription', subscriptionId); + return stripeInstance.subscriptions.update(subscriptionId, params); +} + +function cancelSubscription({ subscriptionId }: { subscriptionId: string }) { + logger.debug('cancel subscription', subscriptionId); + return stripeInstance.subscriptions.del(subscriptionId); +} + +function getListOfInvoices({ customerId }: { customerId: string }) { + logger.debug('getting list of invoices for customer', customerId); + return stripeInstance.invoices.list({ customer: customerId, limit: 100 }); +} + +function stripeWebhookAndCheckoutCallback({ server }: { server: express.Application }) { + server.post( + '/api/v1/public/stripe-invoice-payment-failed', + bodyParser.raw({ type: 'application/json' }), + async (req, res, next) => { + try { + const event = stripeInstance.webhooks.constructEvent( + req.body, + req.headers['stripe-signature'], + dev ? process.env.STRIPE_TEST_ENDPOINTSECRET : process.env.STRIPE_LIVE_ENDPOINTSECRET, + ); + + logger.debug(`${event.id}, ${event.type}`); + + // invoice.payment_failed + // data.object is an invoice + // Occurs whenever an invoice payment attempt fails, due either to a declined payment or to the lack of a stored payment method. + + if (event.type === 'invoice.payment_failed') { + // @ts-expect-error subscription does not exist on type Object + const { subscription } = event.data.object; + logger.debug(JSON.stringify(subscription)); + + await Team.cancelSubscriptionAfterFailedPayment({ + subscriptionId: JSON.stringify(subscription), + }); + } + + res.sendStatus(200); + } catch (err) { + console.error(`Webhook error: ${err.message}`); + next(err); + } + }, + ); + + server.get('/stripe/checkout-completed/:sessionId', async (req, res) => { + const { sessionId } = req.params; + + const session = await retrieveSession({ sessionId }); + if (!session || !session.metadata || !session.metadata.userId || !session.metadata.teamId) { + throw new Error('Wrong session.'); + } + + const user = await User.findById( + session.metadata.userId, + '_id stripeCustomer email displayName isSubscriptionActive stripeSubscription', + ).setOptions({ lean: true }); + + const team = await Team.findById( + session.metadata.teamId, + 'isSubscriptionActive stripeSubscription teamLeaderId slug', + ).setOptions({ lean: true }); + + if (!user) { + throw new Error('User not found.'); + } + + if (!team) { + throw new Error('Team not found.'); + } + + if (team.teamLeaderId !== user._id.toString()) { + throw new Error('Permission denied'); + } + + try { + if (session.mode === 'setup' && session.setup_intent) { + const si: Stripe.SetupIntent = session.setup_intent as Stripe.SetupIntent; + const pm: Stripe.PaymentMethod = si.payment_method as Stripe.PaymentMethod; + + if (user.stripeCustomer) { + await updateCustomer(user.stripeCustomer.id, { + invoice_settings: { default_payment_method: pm.id }, + }); + } + + if (team.stripeSubscription) { + await updateSubscription(team.stripeSubscription.id, { default_payment_method: pm.id }); + } + + await User.changeStripeCard({ session, user }); + } else if (session.mode === 'subscription') { + await User.saveStripeCustomerAndCard({ session, user }); + await Team.subscribeTeam({ session, team }); + await User.getListOfInvoicesForCustomer({ userId: user._id }); + } else { + throw new Error('Wrong session.'); + } + + res.redirect( + `${dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP}/teams/${team.slug}/billing`, + ); + } catch (err) { + console.error(err); + + res.redirect( + `${dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP}/teams/${ + team.slug + }/billing?redirectMessage=${err.message || err.toString()}`, + ); + } + }); +} + +export { createSession, cancelSubscription, getListOfInvoices, stripeWebhookAndCheckoutCallback }; diff --git a/book/11-begin/api/server/utils/slugify.ts b/book/11-begin/api/server/utils/slugify.ts new file mode 100644 index 00000000..f2394edd --- /dev/null +++ b/book/11-begin/api/server/utils/slugify.ts @@ -0,0 +1,43 @@ +import * as _ from 'lodash'; + +const slugify = (text) => _.kebabCase(text); + +async function createUniqueSlug(Model, slug, count, filter) { + const obj = await Model.findOne({ slug: `${slug}-${count}`, ...filter }) + .select('_id') + .setOptions({ lean: true }); + + if (!obj) { + return `${slug}-${count}`; + } + + return createUniqueSlug(Model, slug, count + 1, filter); +} + +async function generateSlug(Model, name, filter = {}) { + const origSlug = slugify(name); + + const obj = await Model.findOne({ slug: origSlug, ...filter }) + .select('_id') + .setOptions({ lean: true }); + + if (!obj) { + return origSlug; + } + + return createUniqueSlug(Model, origSlug, 1, filter); +} + +async function generateNumberSlug(Model, filter = {}, n = 1) { + const obj = await Model.findOne({ slug: n, ...filter }) + .select('_id') + .setOptions({ lean: true }); + + if (!obj) { + return `${n}`; + } + + return generateNumberSlug(Model, filter, ++n); +} + +export { generateSlug, generateNumberSlug }; diff --git a/book/11-begin/api/server/utils/sum.ts b/book/11-begin/api/server/utils/sum.ts new file mode 100644 index 00000000..e51581ac --- /dev/null +++ b/book/11-begin/api/server/utils/sum.ts @@ -0,0 +1,5 @@ +function sum(a, b) { + return a + b; +} + +export { sum }; diff --git a/book/11-begin/api/static/robots.txt b/book/11-begin/api/static/robots.txt new file mode 100644 index 00000000..8a28b4db --- /dev/null +++ b/book/11-begin/api/static/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Disallow: / +Allow: /login +Allow: /signup +Disallow: /* diff --git a/book/11-begin/api/test/server/utils/slugify.test.ts b/book/11-begin/api/test/server/utils/slugify.test.ts new file mode 100644 index 00000000..5a9b8843 --- /dev/null +++ b/book/11-begin/api/test/server/utils/slugify.test.ts @@ -0,0 +1,72 @@ +import * as mongoose from 'mongoose'; +import User from '../../../server/models/User'; +import { generateSlug } from '../../../server/utils/slugify'; + +// eslint-disable-next-line +require('dotenv').config(); + +describe('slugify', () => { + beforeAll(async (done) => { + const options = { + useNewUrlParser: true, + useCreateIndex: true, + useFindAndModify: false, + useUnifiedTopology: true, + }; + + await mongoose.connect(process.env.MONGO_URL_TEST, options); + + const mockUsers = [ + { + slug: 'john', + email: 'john@example.com', + createdAt: new Date(), + displayName: 'abc', + avatarUrl: 'def', + }, + { + slug: 'john-johnson', + email: 'john-johnson@example.com', + createdAt: new Date(), + displayName: 'abc', + avatarUrl: 'def', + }, + { + slug: 'john-johnson-1', + email: 'john-johnson-1@example.com', + createdAt: new Date(), + displayName: 'abc', + avatarUrl: 'def', + }, + ]; + + await User.insertMany(mockUsers); + + done(); + }); + + test('not duplicated', async () => { + expect.assertions(1); + + await expect(generateSlug(User, 'John J Johnson@#$')).resolves.toEqual('john-j-johnson'); + }); + + test('one time duplicated', async () => { + expect.assertions(1); + + await expect(generateSlug(User, ' John@#$')).resolves.toEqual('john-1'); + }); + + test('multiple duplicated', async () => { + expect.assertions(1); + + await expect(generateSlug(User, 'John & Johnson@#$')).resolves.toEqual('john-johnson-2'); + }); + + afterAll(async (done) => { + await User.deleteMany({ slug: { $in: ['john', 'john-johnson', 'john-johnson-1'] } }); + await mongoose.disconnect(); + + done(); + }); +}); diff --git a/book/11-begin/api/test/server/utils/sum.test.ts b/book/11-begin/api/test/server/utils/sum.test.ts new file mode 100644 index 00000000..5fed7c71 --- /dev/null +++ b/book/11-begin/api/test/server/utils/sum.test.ts @@ -0,0 +1,9 @@ +import { sum } from '../../../server/utils/sum'; + +console.log(sum(1, 2)); + +describe.skip('testing sum function', () => { + test('adds 1 + 2 to equal 3', () => { + expect(sum(1, 2)).toBe(3); + }); +}); diff --git a/book/11-begin/api/tsconfig.json b/book/11-begin/api/tsconfig.json new file mode 100644 index 00000000..81b3dbc8 --- /dev/null +++ b/book/11-begin/api/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "jsx": "preserve", + "allowJs": true, + "alwaysStrict": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "removeComments": false, + "preserveConstEnums": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "sourceMap": true, + "skipLibCheck": true, + "baseUrl": ".", + "experimentalDecorators": true, + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015", "es2016"] + }, + "exclude": ["production-server", "node_modules"] +} diff --git a/book/11-begin/api/tsconfig.server.json b/book/11-begin/api/tsconfig.server.json new file mode 100644 index 00000000..40e83ea6 --- /dev/null +++ b/book/11-begin/api/tsconfig.server.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "production-server/" + }, + "include": ["./server/**/*.ts"], + "exclude": ["./server/**/*.test.ts"] +} diff --git a/book/11-begin/api/yarn.lock b/book/11-begin/api/yarn.lock new file mode 100644 index 00000000..32d36024 --- /dev/null +++ b/book/11-begin/api/yarn.lock @@ -0,0 +1,6203 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/core@^7.1.0", "@babel/core@^7.7.5": + version "7.12.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.10.tgz#b79a2e1b9f70ed3d84bbfb6d8c4ef825f606bccd" + integrity sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.10" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.5" + "@babel/parser" "^7.12.10" + "@babel/template" "^7.12.7" + "@babel/traverse" "^7.12.10" + "@babel/types" "^7.12.10" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.12.10", "@babel/generator@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.11.tgz#98a7df7b8c358c9a37ab07a24056853016aba3af" + integrity sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA== + dependencies: + "@babel/types" "^7.12.11" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-function-name@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz#1fd7738aee5dcf53c3ecff24f1da9c511ec47b42" + integrity sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA== + dependencies: + "@babel/helper-get-function-arity" "^7.12.10" + "@babel/template" "^7.12.7" + "@babel/types" "^7.12.11" + +"@babel/helper-get-function-arity@^7.12.10": + version "7.12.10" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz#b158817a3165b5faa2047825dfa61970ddcc16cf" + integrity sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag== + dependencies: + "@babel/types" "^7.12.10" + +"@babel/helper-member-expression-to-functions@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz#aa77bd0396ec8114e5e30787efa78599d874a855" + integrity sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw== + dependencies: + "@babel/types" "^7.12.7" + +"@babel/helper-module-imports@^7.12.1": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb" + integrity sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA== + dependencies: + "@babel/types" "^7.12.5" + +"@babel/helper-module-transforms@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c" + integrity sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w== + dependencies: + "@babel/helper-module-imports" "^7.12.1" + "@babel/helper-replace-supers" "^7.12.1" + "@babel/helper-simple-access" "^7.12.1" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/helper-validator-identifier" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + lodash "^4.17.19" + +"@babel/helper-optimise-call-expression@^7.12.10": + version "7.12.10" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz#94ca4e306ee11a7dd6e9f42823e2ac6b49881e2d" + integrity sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ== + dependencies: + "@babel/types" "^7.12.10" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-replace-supers@^7.12.1": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz#ea511658fc66c7908f923106dd88e08d1997d60d" + integrity sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.12.7" + "@babel/helper-optimise-call-expression" "^7.12.10" + "@babel/traverse" "^7.12.10" + "@babel/types" "^7.12.11" + +"@babel/helper-simple-access@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz#32427e5aa61547d38eb1e6eaf5fd1426fdad9136" + integrity sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-split-export-declaration@^7.11.0", "@babel/helper-split-export-declaration@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz#1b4cc424458643c47d37022223da33d76ea4603a" + integrity sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g== + dependencies: + "@babel/types" "^7.12.11" + +"@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== + +"@babel/helpers@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.5.tgz#1a1ba4a768d9b58310eda516c449913fe647116e" + integrity sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA== + dependencies: + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.5" + "@babel/types" "^7.12.5" + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.12.10", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" + integrity sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz#bcb297c5366e79bebadef509549cd93b04f19978" + integrity sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0" + integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc" + integrity sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.12.7" + "@babel/types" "^7.12.7" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5": + version "7.12.12" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.12.tgz#d0cd87892704edd8da002d674bc811ce64743376" + integrity sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w== + dependencies: + "@babel/code-frame" "^7.12.11" + "@babel/generator" "^7.12.11" + "@babel/helper-function-name" "^7.12.11" + "@babel/helper-split-export-declaration" "^7.12.11" + "@babel/parser" "^7.12.11" + "@babel/types" "^7.12.12" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.12", "@babel/types@^7.12.5", "@babel/types@^7.12.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.12.12" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.12.tgz#4608a6ec313abbd87afa55004d373ad04a96c299" + integrity sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@dabh/diagnostics@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31" + integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" + integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^26.6.2" + jest-util "^26.6.2" + slash "^3.0.0" + +"@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" + micromatch "^4.0.2" + p-each-series "^2.1.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== + dependencies: + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + +"@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== + dependencies: + "@jest/types" "^26.6.2" + "@sinonjs/fake-timers" "^6.0.1" + "@types/node" "*" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" + +"@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" + +"@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^7.0.0" + optionalDependencies: + node-notifier "^8.0.0" + +"@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + +"@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== + dependencies: + "@jest/test-result" "^26.6.2" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + +"@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^26.6.2" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-regex-util "^26.0.0" + jest-util "^26.6.2" + micromatch "^4.0.2" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@nodelib/fs.scandir@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" + integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== + dependencies: + "@nodelib/fs.stat" "2.0.4" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" + integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" + integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== + dependencies: + "@nodelib/fs.scandir" "2.1.4" + fastq "^1.6.0" + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@sinonjs/commons@^1.7.0": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" + integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": + version "7.1.12" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d" + integrity sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8" + integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.0.tgz#0c888dd70b3ee9eebb6e4f200e809da0076262be" + integrity sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.11.0.tgz#b9a1efa635201ba9bc850323a8793ee2d36c04a0" + integrity sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg== + dependencies: + "@babel/types" "^7.3.0" + +"@types/body-parser@*": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" + integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bson@*": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.0.3.tgz#30889d2ffde6262abbe38659364c631454999fbf" + integrity sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw== + dependencies: + "@types/node" "*" + +"@types/component-emitter@^1.2.10": + version "1.2.10" + resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" + integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg== + +"@types/connect-mongo@^3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/connect-mongo/-/connect-mongo-3.1.3.tgz#4ff124a30e530dd2dae4725cfd28ca0b9badbfd1" + integrity sha512-h162kWzfphobvJOttkYKLXEQQmZuOlOSA1IszOusQhguCGf+/B8k4H373SJ0BtVv+qkXP/lziEuUfZDNfzZ1tw== + dependencies: + connect-mongo "*" + +"@types/connect@*": + version "3.4.34" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" + integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== + dependencies: + "@types/node" "*" + +"@types/cookie@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" + integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg== + +"@types/cors@^2.8.8": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.9.tgz#4bd1fcac72eca8d5bec93e76c7fdcbdc1bc2cd4a" + integrity sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg== + +"@types/dotenv@^8.2.0": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-8.2.0.tgz#5cd64710c3c98e82d9d15844375a33bf1b45d053" + integrity sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw== + dependencies: + dotenv "*" + +"@types/express-serve-static-core@*": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.17.tgz#6ba02465165b6c9c3d8db3a28def6b16fc9b70f5" + integrity sha512-YYlVaCni5dnHc+bLZfY908IG1+x5xuibKZMGv8srKkvtul3wUuanYvpIj9GXXoWkQbaAdR+kgX46IETKUALWNQ== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-session@^1.15.8": + version "1.17.3" + resolved "https://registry.yarnpkg.com/@types/express-session/-/express-session-1.17.3.tgz#4a37c5c4428b8f922ac8ac1cb4bd9190a4d2b097" + integrity sha512-57DnyxiqClXOIjoCgeKCUYfKxBPOlOY/k+l1TPK+7bSwyiPTrS5FIk1Ycql7twk4wO7P5lfOVy6akDGiaMSLfw== + dependencies: + "@types/express" "*" + +"@types/express@*", "@types/express@^4.11.1": + version "4.17.9" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.9.tgz#f5f2df6add703ff28428add52bdec8a1091b0a78" + integrity sha512-SDzEIZInC4sivGIFY4Sz1GG6J9UObPwCInYJjko2jzOf/Imx/dlpume6Xxwj1ORL82tBbmN4cPDIDkLbWHk9hw== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.2": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.4.tgz#4ff9f641a7c6d1a3508ff88bc3141b152772e753" + integrity sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" + integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@26.x": + version "26.0.20" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.20.tgz#cd2f2702ecf69e86b586e1f5223a60e454056307" + integrity sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA== + dependencies: + jest-diff "^26.0.0" + pretty-format "^26.0.0" + +"@types/jest@^22.2.3": + version "22.2.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d" + integrity sha512-e74sM9W/4qqWB6D4TWV9FQk0WoHtX1X4FJpbjxucMSVJHtFjbQOH3H6yp+xno4br0AKG0wz/kPtaN599GUOvAg== + +"@types/json-schema@^7.0.3": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" + integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== + +"@types/lodash@^4.14.108": + version "4.14.167" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.167.tgz#ce7d78553e3c886d4ea643c37ec7edc20f16765e" + integrity sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw== + +"@types/mime@*": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" + integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== + +"@types/mongodb@*", "@types/mongodb@^3.5.27": + version "3.6.3" + resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.6.3.tgz#5655af409d9e32d5d5ae9a653abf3e5f9c83eb7a" + integrity sha512-6YNqGP1hk5bjUFaim+QoFFuI61WjHiHE1BNeB41TA00Xd2K7zG4lcWyLLq/XtIp36uMavvS5hoAUJ+1u/GcX2Q== + dependencies: + "@types/bson" "*" + "@types/node" "*" + +"@types/mongoose@^5.5.43": + version "5.10.3" + resolved "https://registry.yarnpkg.com/@types/mongoose/-/mongoose-5.10.3.tgz#3bc6787245aa8ebbff4ed61da18f4775e0ec52cd" + integrity sha512-VfdnaFImXEJZZiuL2ID/ysZs4inOIjxwrAnUgkr5eum2O2BLhFkiSI0i87AwignVva1qWTJ3H3DyM0Rf4USJ4A== + dependencies: + "@types/mongodb" "*" + "@types/node" "*" + +"@types/node-fetch@^1.6.9": + version "1.6.9" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-1.6.9.tgz#a750fb0f4cf2960bf72b462e4c86908022dd69c5" + integrity sha512-n2r6WLoY7+uuPT7pnEtKJCmPUGyJ+cbyBR8Avnu4+m1nzz7DwBVuyIvvlBzCZ/nrpC7rIgb3D6pNavL7rFEa9g== + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@>=8.1.0", "@types/node@^14.14.10": + version "14.14.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340" + integrity sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A== + +"@types/node@^12.12.2": + version "12.19.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.12.tgz#04793c2afa4ce833a9972e4c476432e30f9df47b" + integrity sha512-UwfL2uIU9arX/+/PRcIkT08/iBadGN2z6ExOROA2Dh5mAuWTBj6iJbQX4nekiV5H8cTrEG569LeX+HRco9Cbxw== + +"@types/normalize-package-data@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" + integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + +"@types/passport@^0.4.5": + version "0.4.7" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-0.4.7.tgz#2b7f29bf61df91cf77023b3777e940b613d4b4d8" + integrity sha512-EePlxNYx5tf3n0yjdPXX0/zDOv0UCwjMyQo4UkWGlhHteNDItAj7TfDdLttSThVMKQz3uCW7lsGzMuml0f8g9Q== + dependencies: + "@types/express" "*" + +"@types/prettier@^2.0.0": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.6.tgz#f4b1efa784e8db479cdb8b14403e2144b1e9ff03" + integrity sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA== + +"@types/qs@*": + version "6.9.5" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" + integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== + +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + +"@types/serve-static@*": + version "1.13.8" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.8.tgz#851129d434433c7082148574ffec263d58309c46" + integrity sha512-MoJhSQreaVoL+/hurAZzIm8wafFR6ajiTM1m4A0kv6AGeVBl4r4pOV8bGFrjjq1sGxDTnCoF8i22o0/aE5XCyA== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +"@types/socket.io@^1.4.33": + version "1.4.42" + resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-1.4.42.tgz#cd2750542d95db888e161b88c85d38162f21fbbb" + integrity sha512-2SnWce3DiBVkswhJgpo4FSoMTAAHksxYOFXaBZe4icrrrvpXee8yUPOZT77xzWvLZw63QpREUHHWzM4lQpHjtA== + dependencies: + "@types/node" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" + integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== + +"@types/yargs-parser@*": + version "20.2.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" + integrity sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA== + +"@types/yargs@^15.0.0": + version "15.0.12" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.12.tgz#6234ce3e3e3fa32c5db301a170f96a599c960d74" + integrity sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^4.2.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.12.0.tgz#00d1b23b40b58031e6d7c04a5bc6c1a30a2e834a" + integrity sha512-wHKj6q8s70sO5i39H2g1gtpCXCvjVszzj6FFygneNFyIAxRvNSVz9GML7XpqrB9t7hNutXw+MHnLN/Ih6uyB8Q== + dependencies: + "@typescript-eslint/experimental-utils" "4.12.0" + "@typescript-eslint/scope-manager" "4.12.0" + debug "^4.1.1" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.12.0.tgz#372838e76db76c9a56959217b768a19f7129546b" + integrity sha512-MpXZXUAvHt99c9ScXijx7i061o5HEjXltO+sbYfZAAHxv3XankQkPaNi5myy0Yh0Tyea3Hdq1pi7Vsh0GJb0fA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.12.0" + "@typescript-eslint/types" "4.12.0" + "@typescript-eslint/typescript-estree" "4.12.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^4.2.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.12.0.tgz#e1cf30436e4f916c31fcc962158917bd9e9d460a" + integrity sha512-9XxVADAo9vlfjfoxnjboBTxYOiNY93/QuvcPgsiKvHxW6tOZx1W4TvkIQ2jB3k5M0pbFP5FlXihLK49TjZXhuQ== + dependencies: + "@typescript-eslint/scope-manager" "4.12.0" + "@typescript-eslint/types" "4.12.0" + "@typescript-eslint/typescript-estree" "4.12.0" + debug "^4.1.1" + +"@typescript-eslint/scope-manager@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.12.0.tgz#beeb8beca895a07b10c593185a5612f1085ef279" + integrity sha512-QVf9oCSVLte/8jvOsxmgBdOaoe2J0wtEmBr13Yz0rkBNkl5D8bfnf6G4Vhox9qqMIoG7QQoVwd2eG9DM/ge4Qg== + dependencies: + "@typescript-eslint/types" "4.12.0" + "@typescript-eslint/visitor-keys" "4.12.0" + +"@typescript-eslint/types@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.12.0.tgz#fb891fe7ccc9ea8b2bbd2780e36da45d0dc055e5" + integrity sha512-N2RhGeheVLGtyy+CxRmxdsniB7sMSCfsnbh8K/+RUIXYYq3Ub5+sukRCjVE80QerrUBvuEvs4fDhz5AW/pcL6g== + +"@typescript-eslint/typescript-estree@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.12.0.tgz#3963418c850f564bdab3882ae23795d115d6d32e" + integrity sha512-gZkFcmmp/CnzqD2RKMich2/FjBTsYopjiwJCroxqHZIY11IIoN0l5lKqcgoAPKHt33H2mAkSfvzj8i44Jm7F4w== + dependencies: + "@typescript-eslint/types" "4.12.0" + "@typescript-eslint/visitor-keys" "4.12.0" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.12.0.tgz#a470a79be6958075fa91c725371a83baf428a67a" + integrity sha512-hVpsLARbDh4B9TKYz5cLbcdMIOAoBYgFPCSP9FFS/liSF+b33gVNq8JHY3QGhHNVz85hObvL7BEYLlgx553WCw== + dependencies: + "@typescript-eslint/types" "4.12.0" + eslint-visitor-keys "^2.0.0" + +abab@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-jsx@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-align@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" + integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== + dependencies: + string-width "^3.0.0" + +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +anymatch@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +anymatch@~3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +aws-sdk@^2.796.0: + version "2.824.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.824.0.tgz#a67747d4d0b53d09c6c121e93f44d8f6e76fc44b" + integrity sha512-9KNRQBkIMPn+6DWb4gR+RzqTMNyGLEwOgXbE4dDehOIAflfLnv3IFwLnzrhxJnleB4guYrILIsBroJFBzjiekg== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + +babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== + dependencies: + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/babel__core" "^7.1.7" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + +babel-plugin-istanbul@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" + integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^4.0.0" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== + dependencies: + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base-x@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" + integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== + dependencies: + safe-buffer "^5.0.1" + +base64-arraybuffer@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" + integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= + +base64-js@^1.0.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + +base64url@3.x.x: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +bcrypt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.0.tgz#051407c7cd5ffbfb773d541ca3760ea0754e37e2" + integrity sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg== + dependencies: + node-addon-api "^3.0.0" + node-pre-gyp "0.15.0" + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bl@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" + integrity sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + +bluebird@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== + +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +boxen@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" + integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^3.0.0" + cli-boxes "^2.2.0" + string-width "^4.1.0" + term-size "^2.1.0" + type-fest "^0.8.1" + widest-line "^3.1.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +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" + +bs58@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= + dependencies: + base-x "^3.0.2" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +bson@^1.1.4: + version "1.1.5" + resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.5.tgz#2aaae98fcdf6750c0848b0cba1ddec3c73060a34" + integrity sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg== + +buffer-from@1.x, buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^2.0.0, chalk@^2.1.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chokidar@^3.2.2: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.3.1" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cli-boxes@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +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" + +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" + integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" + integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +colors@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +colorspace@1.1.x: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" + integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== + dependencies: + color "3.0.x" + text-hex "1.0.x" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +component-emitter@^1.2.1, component-emitter@~1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + +connect-mongo@*, connect-mongo@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/connect-mongo/-/connect-mongo-3.2.0.tgz#20f776c7f2a9d8144fc76cfdcbf33edb05eb4d52" + integrity sha512-0Mx88079Z20CG909wCFlR3UxhMYGg6Ibn1hkIje1hwsqOLWtL9HJV+XD0DAjUvQScK6WqY/FA8tSVQM9rR64Rw== + dependencies: + mongodb "^3.1.0" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +cookie@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cors@^2.8.5, cors@~2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^3.2.6: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decimal.js@^10.2.0: + version "10.2.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" + integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +denque@^1.4.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" + integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dotenv@*, dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +emittery@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" + integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +engine.io-parser@~4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.2.tgz#e41d0b3fb66f7bf4a3671d2038a154024edb501e" + integrity sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg== + dependencies: + base64-arraybuffer "0.1.4" + +engine.io@~4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-4.0.6.tgz#7268635d64e2154ab5e7f9ef967ad1744a4c41a9" + integrity sha512-rf7HAVZpcRrcKEKddgIzYUnwg0g5HE1RvJaTLwkcfJmce4g+po8aMuE6vxzp6JwlK8FEq/vi0KWN6tA585DjaA== + dependencies: + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.4.1" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~4.0.0" + ws "~7.4.2" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escodegen@^1.14.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-prettier@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz#5402eb559aa94b894effd6bddfa0b1ca051c858f" + integrity sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA== + +eslint-plugin-prettier@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz#7079cfa2497078905011e6f82e8dd8453d1371b7" + integrity sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1, estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= + +exec-sh@^0.3.2: + version "0.3.4" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" + integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== + dependencies: + "@jest/types" "^26.6.2" + ansi-styles "^4.0.0" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + +express-session@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.1.tgz#36ecbc7034566d38c8509885c044d461c11bf357" + integrity sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q== + dependencies: + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~2.0.0" + on-headers "~1.0.2" + parseurl "~1.3.3" + safe-buffer "5.2.0" + uid-safe "~2.1.5" + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-glob@^3.1.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + +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== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fast-safe-stringify@^2.0.4: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" + integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + +fastq@^1.6.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.0.tgz#74dbefccade964932cdf500473ef302719c652bb" + integrity sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +fecha@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41" + integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg== + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-up@^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" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-minipass@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^2.1.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f" + integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== + +fsevents@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +gensync@^1.0.0-beta.1: + version "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.1: + 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== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^4.0.0, get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.0.0, get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^5.0.0, glob-parent@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" + integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== + dependencies: + ini "1.3.7" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globby@^11.0.1: + version "11.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" + integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + +graceful-fs@^4.1.2: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + +graceful-fs@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +helmet@4.1.0-rc.2: + version "4.1.0-rc.2" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.1.0-rc.2.tgz#12bc8f5c6f25350398fcefb0b522d048db60e934" + integrity sha512-Ywt8Pfs54lvzguQ7JcSDo0+02VxMuDpkmPkt6nt08HHRA1h5UqSdvQquhEtotq9tQCx4icRSjSQpTIB9KU4PBA== + +highlight.js@^10.5.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.5.0.tgz#3f09fede6a865757378f2d9ebdcbc15ba268f98f" + integrity sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw== + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + +ignore-walk@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" + integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== + dependencies: + minimatch "^3.0.4" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +import-fresh@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +inquirer@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-core-module@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-docker@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" + integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + dependencies: + global-dirs "^2.0.1" + is-path-inside "^3.0.1" + +is-npm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" + integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-inside@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-potential-custom-element-name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" + integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +istanbul-lib-coverage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== + +istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" + integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== + dependencies: + "@jest/types" "^26.6.2" + execa "^4.0.0" + throat "^5.0.0" + +jest-cli@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== + dependencies: + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" + prompts "^2.0.1" + yargs "^15.4.1" + +jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" + chalk "^4.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.6.3" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + micromatch "^4.0.2" + pretty-format "^26.6.2" + +jest-diff@^26.0.0, jest-diff@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-docblock@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" + integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== + dependencies: + detect-newline "^3.0.0" + +jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + jest-get-type "^26.3.0" + jest-util "^26.6.2" + pretty-format "^26.6.2" + +jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jsdom "^16.4.0" + +jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== + dependencies: + "@jest/types" "^26.6.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + +jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^26.6.2" + is-generator-fn "^2.0.0" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" + throat "^5.0.0" + +jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== + dependencies: + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== + dependencies: + chalk "^4.0.0" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + pretty-format "^26.6.2" + slash "^3.0.0" + stack-utils "^2.0.2" + +jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + +jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== + dependencies: + "@jest/types" "^26.6.2" + jest-regex-util "^26.0.0" + jest-snapshot "^26.6.2" + +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.2" + read-pkg-up "^7.0.1" + resolve "^1.18.1" + slash "^3.0.0" + +jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.7.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-docblock "^26.0.0" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" + source-map-support "^0.5.6" + throat "^5.0.0" + +jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + cjs-module-lexer "^0.6.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.4.1" + +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + +jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.0.0" + chalk "^4.0.0" + expect "^26.6.2" + graceful-fs "^4.2.4" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + natural-compare "^1.4.0" + pretty-format "^26.6.2" + semver "^7.3.2" + +jest-util@^26.1.0, jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + +jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== + dependencies: + "@jest/types" "^26.6.2" + camelcase "^6.0.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" + leven "^3.1.0" + pretty-format "^26.6.2" + +jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== + dependencies: + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^26.6.2" + string-length "^4.0.1" + +jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest@^26.4.2: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" + integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== + dependencies: + "@jest/core" "^26.6.3" + import-local "^3.0.2" + jest-cli "^26.6.3" + +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^16.4.0: + version "16.4.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" + integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w== + dependencies: + abab "^2.0.3" + acorn "^7.1.1" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.2.0" + data-urls "^2.0.0" + decimal.js "^10.2.0" + domexception "^2.0.1" + escodegen "^1.14.1" + html-encoding-sniffer "^2.0.1" + is-potential-custom-element-name "^1.0.0" + nwsapi "^2.2.0" + parse5 "5.1.1" + request "^2.88.2" + request-promise-native "^1.0.8" + saxes "^5.0.0" + symbol-tree "^3.2.4" + tough-cookie "^3.0.1" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + ws "^7.2.3" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@2.x, json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kareem@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.2.tgz#78c4508894985b8d38a0dc15e1a8e11078f2ca93" + integrity sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ== + +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + +latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +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" + +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.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + +logform@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2" + integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg== + dependencies: + colors "^1.2.1" + fast-safe-stringify "^2.0.4" + fecha "^4.2.0" + ms "^2.1.1" + triple-beam "^1.3.0" + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@1.x, make-error@^1.1.1: + 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.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +marked@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.7.tgz#6e14b595581d2319cdcf033a24caaf41455a01fb" + integrity sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +mime-db@1.45.0, "mime-db@>= 1.43.0 < 2": + version "1.45.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" + integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== + +mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.28" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" + integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== + dependencies: + mime-db "1.45.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@1.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mongodb@3.6.3, mongodb@^3.1.0: + version "3.6.3" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.6.3.tgz#eddaed0cc3598474d7a15f0f2a5b04848489fd05" + integrity sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w== + dependencies: + bl "^2.2.1" + bson "^1.1.4" + denque "^1.4.1" + require_optional "^1.0.1" + safe-buffer "^5.1.2" + optionalDependencies: + saslprep "^1.0.0" + +mongoose-legacy-pluralize@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4" + integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ== + +mongoose@^5.10.15: + version "5.11.11" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.11.11.tgz#f0f1b3691385aa8cf0e4acdec20f1851bb6f9660" + integrity sha512-JgKKAosJf6medPOZi2LmO7sMz7Sg00mgjyPAKari3alzL+R/n8D+zKK29iGtJpNNtv9IKy14H37CWuiaZ7016w== + dependencies: + "@types/mongodb" "^3.5.27" + bson "^1.1.4" + kareem "2.3.2" + mongodb "3.6.3" + mongoose-legacy-pluralize "1.0.2" + mpath "0.8.3" + mquery "3.2.3" + ms "2.1.2" + regexp-clone "1.0.0" + safe-buffer "5.2.1" + sift "7.0.1" + sliced "1.0.1" + +mpath@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.3.tgz#828ac0d187f7f42674839d74921970979abbdd8f" + integrity sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA== + +mquery@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.3.tgz#bcf54fdfe3baf57b6a22f9b62b1ad5fa18ffe96a" + integrity sha512-cIfbP4TyMYX+SkaQ2MntD+F2XbqaBHUYWk3j+kqdDztPWok3tgyssOZxMHMtzbV1w9DaSlvEea0Iocuro41A4g== + dependencies: + bluebird "3.5.1" + debug "3.1.0" + regexp-clone "^1.0.0" + safe-buffer "5.1.2" + sliced "1.0.1" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +needle@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe" + integrity sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-addon-api@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" + integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw== + +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1" + integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA== + dependencies: + growly "^1.3.0" + is-wsl "^2.2.0" + semver "^7.3.2" + shellwords "^0.1.1" + uuid "^8.3.0" + which "^2.0.2" + +node-pre-gyp@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz#c2fc383276b74c7ffa842925241553e8b40f1087" + integrity sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.3" + needle "^2.5.0" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4.4.2" + +nodemon@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.7.tgz#6f030a0a0ebe3ea1ba2a38f71bf9bab4841ced32" + integrity sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA== + dependencies: + chokidar "^3.2.2" + debug "^3.2.6" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.7" + semver "^5.7.1" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.3" + update-notifier "^4.1.0" + +nopt@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + +npm-bundled@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" + integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-packlist@^1.1.6: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +oauth@0.9.x: + version "0.9.15" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= + +object-assign@^4, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.1, optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-each-series@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +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-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-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@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" + integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +passport-google-oauth1@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc" + integrity sha1-r3SoA99R7GRvZqRNgigr5vEI4Mw= + dependencies: + passport-oauth1 "1.x.x" + +passport-google-oauth20@2.x.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef" + integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ== + dependencies: + passport-oauth2 "1.x.x" + +passport-google-oauth@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz#f6eb4bc96dd6c16ec0ecfdf4e05ec48ca54d4dae" + integrity sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA== + dependencies: + passport-google-oauth1 "1.x.x" + passport-google-oauth20 "2.x.x" + +passport-oauth1@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.1.0.tgz#a7de988a211f9cf4687377130ea74df32730c918" + integrity sha1-p96YiiEfnPRoc3cTDqdN8ycwyRg= + dependencies: + oauth "0.9.x" + passport-strategy "1.x.x" + utils-merge "1.x.x" + +passport-oauth2@1.x.x: + version "1.5.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108" + integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ== + dependencies: + base64url "3.x.x" + oauth "0.9.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + +passport-strategy@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= + +passport@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" + integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + +passwordless-tokenstore@^0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/passwordless-tokenstore/-/passwordless-tokenstore-0.0.10.tgz#829c4c0b792ac2c55de54c05c118d79e946b9b2e" + integrity sha1-gpxMC3kqwsVd5UwFwRjXnpRrmy4= + +passwordless@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/passwordless/-/passwordless-1.1.3.tgz#fca25954dd6eeb4ffde2020d1418eab337f7e862" + integrity sha512-Qwq7D/gc1kYwcFtpe5M0BvKeNb8wDh3QQVpWkEfI9+86v80HsGJpbmw4AvJc378u2tVjCzoD8UR6EjXVeZcjeA== + dependencies: + bs58 "^4.0.1" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" + integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== + +pretty-format@^26.0.0, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +prompts@^2.0.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" + integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +pstree.remy@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +pupa@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@^6.6.0: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc@^1.2.7, rc@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-is@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" + integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +readable-stream@^2.0.6, readable-stream@^2.3.5, readable-stream@^2.3.7: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== + dependencies: + picomatch "^2.2.1" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexp-clone@1.0.0, regexp-clone@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" + integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw== + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regexpp@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== + dependencies: + rc "^1.2.8" + +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== + dependencies: + lodash "^4.17.19" + +request-promise-native@^1.0.8: + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== + dependencies: + request-promise-core "1.1.4" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +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== + +require_optional@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" + integrity sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g== + dependencies: + resolve-from "^2.0.0" + semver "^5.1.0" + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" + integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.10.0, resolve@^1.18.1: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^2.6.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.1.10" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef" + integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw== + +rxjs@^6.6.0: + version "6.6.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" + integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +saslprep@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + +sax@>=0.6.0, sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.x, semver@^7.3.2: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + +semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +sift@7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08" + integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g== + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +sliced@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" + integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +socket.io-adapter@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.0.3.tgz#372b7cde7a535fc4f4f0d5ac7f73952a3062d438" + integrity sha512-2wo4EXgxOGSFueqvHAdnmi5JLZzWqMArjuP4nqC26AtLh5PoCPsaRbRdah2xhcwTAMooZfjYiNVNkkmmSMaxOQ== + +socket.io-parser@~4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.3.tgz#2c494f2de1e7c1b40a14ba1512227e9798d8b10e" + integrity sha512-m4ybFiP4UYVORRt7jcdqf8UWx+ywVdAqqsJyruXxAdD3Sv6MDemijWij34mOWdMJ55bEdIb9jACBhxUgNK6sxw== + dependencies: + "@types/component-emitter" "^1.2.10" + component-emitter "~1.3.0" + debug "~4.3.1" + +socket.io@^3.0.3: + version "3.0.5" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.0.5.tgz#b4328116b6b34f76270725a4e35b75e2348d2264" + integrity sha512-5yWQ43P/4IttmPCGKDQ3CVocBiJWGpibyhYJxgUhf69EHMzmK8XW0DkmHIoYdLmZaVZJyiEkUqpeC7rSCIqekw== + dependencies: + "@types/cookie" "^0.4.0" + "@types/cors" "^2.8.8" + "@types/node" "^14.14.10" + accepts "~1.3.4" + base64id "~2.0.0" + debug "~4.3.1" + engine.io "~4.0.6" + socket.io-adapter "~2.0.3" + socket.io-parser "~4.0.3" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.17, source-map-support@^0.5.6: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= + dependencies: + memory-pager "^1.0.2" + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" + integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + +stack-utils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + dependencies: + escape-string-regexp "^2.0.0" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +string-length@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" + integrity sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.0.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +stripe@^8.129.0: + version "8.130.0" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.130.0.tgz#e52a5ee6538a70ce76379364bd3839ca7ea925ab" + integrity sha512-9e283EFhxDz7SUcgNiUFRdTZ/kS2IkoT0KBMOJHdf3vY+mvURq355s2E0Zyy9rtNmt+CEZ0nCMiZ3PqIqpp6Pg== + dependencies: + "@types/node" ">=8.1.0" + qs "^6.6.0" + +supports-color@^5.3.0, supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47" + integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +tar@^4.4.2: + version "4.4.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" + integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.8.6" + minizlib "^1.2.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.3" + +term-size@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" + integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + +tough-cookie@^2.3.3, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== + dependencies: + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" + integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== + dependencies: + punycode "^2.1.1" + +triple-beam@^1.2.0, triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" + integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== + +ts-jest@^26.4.4: + version "26.4.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.4.tgz#61f13fb21ab400853c532270e52cc0ed7e502c49" + integrity sha512-3lFWKbLxJm34QxyVNNCgXX1u4o/RV0myvA2y2Bxm46iGIjKlaY0own9gIckbjZJPn+WaJEnfPPJ20HHGpoq4yg== + dependencies: + "@types/jest" "26.x" + bs-logger "0.x" + buffer-from "1.x" + fast-json-stable-stringify "2.x" + jest-util "^26.1.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + mkdirp "1.x" + semver "7.x" + yargs-parser "20.x" + +ts-node@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" + integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== + dependencies: + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + +tslib@^1.8.1, tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.17.1: + version "3.19.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.19.0.tgz#9387cb5fcb71579aa0909c509604f8a7fbe1cff1" + integrity sha512-A7BaLUPvcQ1cxVu72YfD+UMI3SQPTDv/w4ol6TOwLyI0hwfG9EC+cYlhdflJTmtYTgZ3KqdPSe/otxU4K3kArg== + dependencies: + tslib "^1.8.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" + integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== + +uid-safe@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" + +uid2@0.0.x: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I= + +undefsafe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" + integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== + dependencies: + debug "^2.2.0" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +update-notifier@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" + integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== + dependencies: + boxen "^4.2.0" + chalk "^3.0.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.3.1" + is-npm "^4.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + pupa "^2.0.1" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + +uri-js@^4.2.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" + integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +utils-merge@1.0.1, utils-merge@1.x.x: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache@^2.0.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" + integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== + +v8-to-istanbul@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz#5b95cef45c0f83217ec79f8fc7ee1c8b486aee07" + integrity sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" + +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^8.0.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837" + integrity sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^2.0.2" + webidl-conversions "^6.1.0" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + +winston-transport@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59" + integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw== + dependencies: + readable-stream "^2.3.7" + triple-beam "^1.2.0" + +winston@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" + integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== + dependencies: + "@dabh/diagnostics" "^2.0.2" + async "^3.1.0" + is-stream "^2.0.0" + logform "^2.2.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.4.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +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" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +ws@^7.2.3, ws@~7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.2.tgz#782100048e54eb36fe9843363ab1c68672b261dd" + integrity sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA== + +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + +xmlchars@^2.2.0: + version "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.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" + integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== + +yallist@^3.0.0, yallist@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@20.x: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +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@^15.4.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" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/book/11-begin/app/.babelrc b/book/11-begin/app/.babelrc new file mode 100644 index 00000000..e3d469b6 --- /dev/null +++ b/book/11-begin/app/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "next/babel", + { + "class-properties": { "loose": true } + } + ] + ] +} diff --git a/book/11-begin/app/.elasticbeanstalk/config.yml b/book/11-begin/app/.elasticbeanstalk/config.yml new file mode 100644 index 00000000..d567ce9c --- /dev/null +++ b/book/11-begin/app/.elasticbeanstalk/config.yml @@ -0,0 +1,19 @@ +branch-defaults: + default: + environment: app-saas-boilerplate +environment-defaults: + app-saas-boilerplate: + branch: null + repository: null +global: + application_name: book + default_ec2_keyname: null + default_platform: Node.js 12 running on 64bit Amazon Linux 2 + default_region: us-east-1 + include_git_submodules: true + instance_profile: null + platform_name: null + platform_version: null + profile: null + sc: null + workspace_type: Application diff --git a/book/11-begin/app/.eslintignore b/book/11-begin/app/.eslintignore new file mode 100644 index 00000000..ff4515a6 --- /dev/null +++ b/book/11-begin/app/.eslintignore @@ -0,0 +1,3 @@ +.next +production-server +node_modules diff --git a/book/11-begin/app/.eslintrc.js b/book/11-begin/app/.eslintrc.js new file mode 100644 index 00000000..bf61beaa --- /dev/null +++ b/book/11-begin/app/.eslintrc.js @@ -0,0 +1,31 @@ +module.exports = { + settings: { + react: { version: 'detect' }, + }, + parser: '@typescript-eslint/parser', + extends: ['plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], + env: { + es6: true, + node: true, + }, + plugins: ['prettier', 'react'], + rules: { + 'prettier/prettier': [ + 'error', + { + singleQuote: true, + trailingComma: 'all', + arrowParens: 'always', + printWidth: 100, + semi: true, + }, + ], + '@typescript-eslint/camelcase': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + 'react/no-unescaped-entities': 'off', + 'react/jsx-filename-extension': ['error', { extensions: ['.tsx'] }], + '@typescript-eslint/no-explicit-any': 'off', + 'prefer-arrow-callback': 'error', + '@typescript-eslint/explicit-module-boundary-types': 'off', + }, +}; \ No newline at end of file diff --git a/book/11-begin/app/.gitignore b/book/11-begin/app/.gitignore new file mode 100644 index 00000000..69968ac7 --- /dev/null +++ b/book/11-begin/app/.gitignore @@ -0,0 +1,21 @@ +*~ +*.swp +tmp/ +npm-debug.log +.DS_Store + + + +.build/* +.next +.vscode/ +node_modules/ +.coverage +.env +now.json +.note + +compiled/ +production-server/ + +yarn-error.log diff --git a/book/11-begin/app/components/common/Confirmer.tsx b/book/11-begin/app/components/common/Confirmer.tsx new file mode 100644 index 00000000..c89dc3be --- /dev/null +++ b/book/11-begin/app/components/common/Confirmer.tsx @@ -0,0 +1,71 @@ +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import React from 'react'; + +export let openConfirmDialogExternal; + +type State = { + open: boolean; + title: string; + message: string; + onAnswer: (answer) => void; +}; + +class Confirmer extends React.Component { + constructor(props) { + super(props); + + this.state = { + open: false, + title: 'Are you sure?', + message: '', + onAnswer: null, + }; + + openConfirmDialogExternal = this.openConfirmDialog; + } + + public render() { + return ( + + {this.state.title} + + {this.state.message} + + + + + + + ); + } + + public handleClose = () => { + this.setState({ open: false }); + this.state.onAnswer(false); + }; + + public handleYes = () => { + this.setState({ open: false }); + this.state.onAnswer(true); + }; + + public openConfirmDialog = ({ title, message, onAnswer }) => { + this.setState({ open: true, title, message, onAnswer }); + }; +} + +export default Confirmer; diff --git a/book/11-begin/app/components/common/LoginButton.tsx b/book/11-begin/app/components/common/LoginButton.tsx new file mode 100644 index 00000000..22597ede --- /dev/null +++ b/book/11-begin/app/components/common/LoginButton.tsx @@ -0,0 +1,90 @@ +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import React from 'react'; + +import { emailLoginLinkApiMethod } from '../../lib/api/public'; +import notify from '../../lib/notify'; +import { makeQueryString } from '../../lib/api/makeQueryString'; + +const dev = process.env.NODE_ENV !== 'production'; + +type Props = { invitationToken?: string }; +type State = { email: string }; + +class LoginButton extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { email: '' }; + } + + public render() { + const { invitationToken } = this.props; + + let url = `${dev ? process.env.URL_API : process.env.PRODUCTION_URL_API}/auth/google`; + const qs = makeQueryString({ invitationToken }); + + if (qs) { + url += `?${qs}`; + } + + // console.log(url); + + return ( + + +

+
+


OR


+

+
+

+
+ { + this.setState({ email: event.target.value }); + }} + style={{ width: '300px' }} + /> +

+ +

+

+
+

+
+ ); + } + + private onSubmit = async (event) => { + event.preventDefault(); + const { email } = this.state; + const { invitationToken } = this.props; + + if (!email) { + notify('Email is required'); + } + + try { + await emailLoginLinkApiMethod({ email, invitationToken }); + this.setState({ email: '' }); + notify('SaaS boilerplate emailed you a login link.'); + } catch (error) { + notify(error); + } + }; +} + +export default LoginButton; diff --git a/book/11-begin/app/components/common/MemberChooser.tsx b/book/11-begin/app/components/common/MemberChooser.tsx new file mode 100644 index 00000000..eb5be516 --- /dev/null +++ b/book/11-begin/app/components/common/MemberChooser.tsx @@ -0,0 +1,78 @@ +import React from 'react'; + +import Autocomplete from '@material-ui/lab/Autocomplete'; +import TextField from '@material-ui/core/TextField'; + +import { User } from '../../lib/store/user'; + +type Props = { + onChange: (item) => void; + selectedMemberIds?: string[]; + members: User[]; + label?: string; + helperText?: string; +}; + +type State = { + selectedItems: { label: string; id: string }[]; +}; + +class MemberChooser extends React.Component { + constructor(props) { + super(props); + + const suggestions = this.props.members.map((user) => ({ + label: user.displayName || user.email, + id: user._id, + })); + + const selectedItems = suggestions.filter( + (s) => this.props.selectedMemberIds.indexOf(s.id) !== -1, + ); + + this.state = { + selectedItems: selectedItems || [], + }; + } + + public render() { + const suggestions = this.props.members.map((user) => ({ + label: user.displayName || user.email, + id: user._id, + })); + + return ( + option.label} + getOptionSelected={(option, value) => option.id === value.id} + value={this.state.selectedItems} + renderInput={(params) => ( + + )} + onChange={this.handleChange} + filterSelectedOptions={true} + noOptionsText="No team members to select from" + /> + ); + } + + public handleChange = (event, value) => { + event.preventDefault(); + + const selectedItems = value; + + this.setState({ selectedItems }); + + this.props.onChange(selectedItems.map((i) => i.id)); + }; +} + +export default MemberChooser; diff --git a/book/11-begin/app/components/common/MenuWithLinks.tsx b/book/11-begin/app/components/common/MenuWithLinks.tsx new file mode 100644 index 00000000..77b21f3e --- /dev/null +++ b/book/11-begin/app/components/common/MenuWithLinks.tsx @@ -0,0 +1,95 @@ +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import Link from 'next/link'; +import { NextRouter, withRouter } from 'next/router'; +import React from 'react'; + +type Props = { + options: { + href: string; + as: string; + highlighterSlug: string; + text: string; + externalServer: boolean; + separator: boolean; + }[]; + router: NextRouter; +}; + +type State = { + anchorEl: Element | ((element: Element) => Element); +}; + +class MenuWithLinks extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + anchorEl: null, + }; + } + + public render() { + const { options, children, router } = this.props; + const { anchorEl } = this.state; + + return ( +
+
+ {children} +
+ + {options.map((option, i) => + option.separator ? ( +
+ ) : option.externalServer ? ( + { + event.preventDefault(); + window.location.href = option.href; + this.handleClose(); + }} + key={option.href || option.text} + > + {option.text} + + ) : ( + + + {option.text} + + + ), + )} +
+
+ ); + } + + public handleClick = (event) => { + this.setState({ anchorEl: event.currentTarget }); + }; + + public handleClose = () => { + this.setState({ anchorEl: null }); + }; +} + +export default withRouter(MenuWithLinks); diff --git a/book/11-begin/app/components/common/MenuWithMenuItems.tsx b/book/11-begin/app/components/common/MenuWithMenuItems.tsx new file mode 100644 index 00000000..bacd60ae --- /dev/null +++ b/book/11-begin/app/components/common/MenuWithMenuItems.tsx @@ -0,0 +1,72 @@ +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import React from 'react'; + +type Props = { + menuOptions: any; + itemOptions: any[]; +}; + +type State = { + menuElem: Element | ((element: Element) => Element); +}; + +class MenuWithMenuItems extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + menuElem: null, + }; + } + + public render() { + const { menuOptions, itemOptions } = this.props; + const { menuElem } = this.state; + + return ( +
+ this.handleClick(e)} + /> + + + {itemOptions.map((option, i) => ( + { + this.setState({ menuElem: null }); + option.onClick(e); + }} + > + {option.text} + + ))} + +
+ ); + } + + public handleClick = (event) => { + event.preventDefault(); + this.setState({ menuElem: event.currentTarget }); + }; + + public handleClose = () => { + this.setState({ menuElem: null }); + }; +} + +export default MenuWithMenuItems; diff --git a/book/11-begin/app/components/common/Notifier.tsx b/book/11-begin/app/components/common/Notifier.tsx new file mode 100644 index 00000000..04428c31 --- /dev/null +++ b/book/11-begin/app/components/common/Notifier.tsx @@ -0,0 +1,53 @@ +import Snackbar from '@material-ui/core/Snackbar'; +import React from 'react'; + +export let openSnackbarExternal; + +type State = { + open: boolean; + message: string; +}; + +class Notifier extends React.PureComponent { + constructor(props) { + super(props); + openSnackbarExternal = this.openSnackbar; + + this.state = { + open: false, + message: '', + }; + } + + public render() { + const message = ( + + ); + + return ( + + ); + } + + public handleSnackbarClose = () => { + this.setState({ + open: false, + message: '', + }); + }; + + public openSnackbar = ({ message }) => { + this.setState({ open: true, message }); + }; +} + +export default Notifier; diff --git a/book/11-begin/app/components/discussions/CreateDiscussionForm.tsx b/book/11-begin/app/components/discussions/CreateDiscussionForm.tsx new file mode 100644 index 00000000..98c1dea0 --- /dev/null +++ b/book/11-begin/app/components/discussions/CreateDiscussionForm.tsx @@ -0,0 +1,269 @@ +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import InputLabel from '@material-ui/core/InputLabel'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; +import TextField from '@material-ui/core/TextField'; +import { observer } from 'mobx-react'; +import Head from 'next/head'; +import Router from 'next/router'; +import NProgress from 'nprogress'; +import React from 'react'; + +import notify from '../../lib/notify'; +import { Store } from '../../lib/store'; +import MemberChooser from '../common/MemberChooser'; +import PostEditor from '../posts/PostEditor'; + +type Props = { + isMobile: boolean; + store: Store; + open: boolean; + onClose: () => void; +}; + +type State = { + name: string; + memberIds: string[]; + disabled: boolean; + content: string; + notificationType: string; +}; + +class CreateDiscussionForm extends React.Component { + constructor(props) { + super(props); + + this.state = { + name: '', + memberIds: [], + disabled: false, + content: '', + notificationType: 'default', + }; + } + public render() { + const { open, isMobile, store } = this.props; + const { currentTeam, currentUser } = store; + + const membersMinusCreator = Array.from(currentTeam.members.values()).filter( + (user) => user._id !== currentUser._id, + ); + + return ( + + {open ? ( + + New Discussion + + + ) : null} + + Create new Discussion + +
+
+

+
+ { + this.setState({ name: event.target.value }); + }} + /> +
+

+ +

+
+ + Notification type + + + Choose how to notify members about new Posts inside Discussion. + + +

+
+

+ + {isMobile ?

: null} + {' '} +

+

+ this.setState({ content })} + members={Array.from(store.currentTeam.members.values())} + store={store} + /> +

+

+ + {isMobile ?

: null} + {' '} +

+
+
+

+
+
+
+
+ ); + } + + public handleMembersChange = (memberIds) => { + this.setState({ memberIds }); + }; + + public handleClose = () => { + this.setState({ + name: '', + memberIds: [], + disabled: false, + content: '', + notificationType: 'default', + }); + this.props.onClose(); + }; + + private onSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + const { store } = this.props; + const { currentTeam } = store; + + if (!currentTeam) { + notify('Team have not selected'); + return; + } + + const { name, memberIds, content, notificationType } = this.state; + + if (!name) { + notify('Name is required'); + return; + } + + if (!content) { + notify('Content is required'); + return; + } + + if (!memberIds || memberIds.length < 1) { + notify('Please assign at least one person to this Discussion.'); + return; + } + + if (!notificationType) { + notify('Please select notification type.'); + return; + } + + this.setState({ disabled: true }); + NProgress.start(); + + console.log(notificationType); + + try { + const discussion = await currentTeam.addDiscussion({ + name, + memberIds, + notificationType, + }); + + const post = await discussion.addPost(content); + + const dev = process.env.NODE_ENV !== 'production'; + + if (discussion.notificationType === 'email') { + const userIdsForLambda = discussion.memberIds.filter((m) => m !== discussion.createdUserId); + + await discussion.sendDataToLambda({ + discussionName: discussion.name, + discussionLink: `${dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP}/teams/${ + discussion.team.slug + }/discussions/${discussion.slug}`, + postContent: post.content, + authorName: post.user.displayName, + userIds: userIdsForLambda, + }); + } + + this.setState({ name: '', memberIds: [], content: '', notificationType: 'default' }); + + notify('You successfully added new Discussion.'); + + Router.push( + `/discussion?teamSlug=${currentTeam.slug}&discussionSlug=${discussion.slug}`, + `/teams/${currentTeam.slug}/discussions/${discussion.slug}`, + ); + } catch (error) { + console.log(error); + notify(error); + } finally { + this.setState({ disabled: false }); + NProgress.done(); + this.props.onClose(); + } + }; +} + +export default observer(CreateDiscussionForm); diff --git a/book/11-begin/app/components/discussions/DiscussionActionMenu.tsx b/book/11-begin/app/components/discussions/DiscussionActionMenu.tsx new file mode 100644 index 00000000..e3cec615 --- /dev/null +++ b/book/11-begin/app/components/discussions/DiscussionActionMenu.tsx @@ -0,0 +1,179 @@ +import { observer } from 'mobx-react'; +import NProgress from 'nprogress'; +import React from 'react'; + +import confirm from '../../lib/confirm'; +import notify from '../../lib/notify'; +import { Store } from '../../lib/store'; +import { Discussion } from '../../lib/store/discussion'; + +import MenuWithMenuItems from '../common/MenuWithMenuItems'; +import EditDiscussionForm from './EditDiscussionForm'; + +const dev = process.env.NODE_ENV !== 'production'; + +const getMenuOptions = (discussion) => ({ + dataId: discussion._id, + id: `discussion-menu-${discussion._id}`, +}); + +const getMenuItemOptionsForCreator = (discussion, component) => [ + { + text: 'Copy URL', + dataId: discussion._id, + onClick: component.handleCopyUrl, + }, + { + text: 'Edit', + dataId: discussion._id, + onClick: component.editDiscussion, + }, + { + text: 'Delete', + dataId: discussion._id, + onClick: component.deleteDiscussion, + }, +]; + +const getMenuItemOptions = (discussion, component) => [ + { + text: 'Copy URL', + dataId: discussion._id, + onClick: component.handleCopyUrl, + }, +]; + +type Props = { + discussion: Discussion; + store: Store; + isMobile: boolean; +}; + +type State = { + discussionFormOpen: boolean; + selectedDiscussion: Discussion; +}; + +class DiscussionActionMenu extends React.Component { + constructor(props) { + super(props); + + this.state = { + discussionFormOpen: false, + selectedDiscussion: null, + }; + } + + public render() { + const { discussion, store } = this.props; + const { currentUser } = store; + + const isCreator = currentUser._id === discussion.createdUserId ? true : false; + + return ( + + + + {this.state.discussionFormOpen ? ( + + ) : null} + + ); + } + + public handleCopyUrl = async (event) => { + const { store } = this.props; + const { currentTeam } = store; + + const id = event.currentTarget.dataset.id; + if (!id) { + return; + } + + const selectedDiscussion = currentTeam.discussions.find((d) => d._id === id); + + const discussionUrl = `${dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP}/teams/${ + currentTeam.slug + }/discussions/${selectedDiscussion.slug}`; + + try { + if (window.navigator) { + await window.navigator.clipboard.writeText(discussionUrl); + notify('You successfully copied URL.'); + } + } catch (err) { + notify(err); + } finally { + this.setState({ discussionFormOpen: false, selectedDiscussion: null }); + } + }; + + public editDiscussion = (event) => { + const { currentTeam } = this.props.store; + if (!currentTeam) { + notify('You have not selected Team.'); + return; + } + + const id = event.currentTarget.dataset.id; + if (!id) { + return; + } + + const selectedDiscussion = currentTeam.discussions.find((d) => d._id === id); + + this.setState({ discussionFormOpen: true, selectedDiscussion }); + }; + + public deleteDiscussion = async (event) => { + const { currentTeam } = this.props.store; + if (!currentTeam) { + notify('You have not selected Team.'); + return; + } + + const id = event.currentTarget.dataset.id; + + confirm({ + title: 'Are you sure?', + message: '', + onAnswer: async (answer) => { + if (!answer) { + return; + } + + NProgress.start(); + + try { + await currentTeam.deleteDiscussion(id); + + notify('You successfully deleted Discussion.'); + } catch (error) { + console.error(error); + notify(error); + } finally { + NProgress.done(); + } + }, + }); + }; + + public handleDiscussionFormClose = () => { + this.setState({ discussionFormOpen: false, selectedDiscussion: null }); + }; +} + +export default observer(DiscussionActionMenu); diff --git a/book/11-begin/app/components/discussions/DiscussionList.tsx b/book/11-begin/app/components/discussions/DiscussionList.tsx new file mode 100644 index 00000000..5ca847ac --- /dev/null +++ b/book/11-begin/app/components/discussions/DiscussionList.tsx @@ -0,0 +1,88 @@ +import Tooltip from '@material-ui/core/Tooltip'; +import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; +import { observer } from 'mobx-react'; +import React from 'react'; + +import { Store } from '../../lib/store'; +import { Team } from '../../lib/store/team'; + +import CreateDiscussionForm from './CreateDiscussionForm'; +import DiscussionListItem from './DiscussionListItem'; + +import notify from '../../lib/notify'; + +type Props = { store: Store; team: Team; isMobile: boolean }; + +type State = { discussionFormOpen: boolean }; + +class DiscussionList extends React.Component { + constructor(props) { + super(props); + + this.state = { + discussionFormOpen: false, + }; + } + + public componentDidMount() { + this.props.team.loadDiscussions().catch((err) => notify(err)); + } + + public componentDidUpdate(prevProps: Props) { + if (this.props.team._id !== prevProps.team._id) { + this.props.team.loadDiscussions().catch((err) => notify(err)); + } + } + + public render() { + const { store, team } = this.props; + + const isThemeDark = store && store.currentUser && store.currentUser.darkTheme === true; + + return ( +
+ Discussions + + + {' '} + + +

+

    + {team && + team.orderedDiscussions.map((d) => { + return ( + + ); + })} +
+ +
+ ); + } + + public addDiscussion = (event) => { + event.preventDefault(); + this.setState({ discussionFormOpen: true }); + }; + + public handleDiscussionFormClose = () => { + this.setState({ discussionFormOpen: false }); + }; +} + +export default observer(DiscussionList); diff --git a/book/11-begin/app/components/discussions/DiscussionListItem.tsx b/book/11-begin/app/components/discussions/DiscussionListItem.tsx new file mode 100644 index 00000000..a66a4024 --- /dev/null +++ b/book/11-begin/app/components/discussions/DiscussionListItem.tsx @@ -0,0 +1,74 @@ +import Paper from '@material-ui/core/Paper'; +import { observer } from 'mobx-react'; +import Link from 'next/link'; +import React from 'react'; + +import { Store } from '../../lib/store'; +import { Discussion } from '../../lib/store/discussion'; +import { Team } from '../../lib/store/team'; + +import DiscussionActionMenu from './DiscussionActionMenu'; + +type Props = { + store: Store; + discussion: Discussion; + team: Team; + isMobile: boolean; +}; + +class DiscussionListItem extends React.Component { + public render() { + const { store, discussion, team, isMobile } = this.props; + const trimmingLength = 16; + + const selectedDiscussion = + store.currentUrl === `/teams/${team.slug}/discussions/${discussion.slug}`; + + console.log(store.currentUrl); + + const isThemeDark = store && store.currentUser && store.currentUser.darkTheme === true; + + const selectedItemBorder = isThemeDark + ? '1px rgba(255, 255, 255, 0.75) solid' + : '1px rgba(0, 0, 0, 0.75) solid'; + + return ( + +
  • + + + {discussion.name.length > trimmingLength + ? `${discussion.name.substring(0, trimmingLength)}...` + : discussion.name} + + +
    + +
    +
  • +
    + ); + } +} + +export default observer(DiscussionListItem); diff --git a/book/11-begin/app/components/discussions/EditDiscussionForm.tsx b/book/11-begin/app/components/discussions/EditDiscussionForm.tsx new file mode 100644 index 00000000..8a61765c --- /dev/null +++ b/book/11-begin/app/components/discussions/EditDiscussionForm.tsx @@ -0,0 +1,205 @@ +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import InputLabel from '@material-ui/core/InputLabel'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; +import TextField from '@material-ui/core/TextField'; +import { observer } from 'mobx-react'; +import NProgress from 'nprogress'; +import React from 'react'; + +import notify from '../../lib/notify'; +import { Store } from '../../lib/store'; +import { Discussion } from '../../lib/store/discussion'; +import MemberChooser from '../common/MemberChooser'; + +type Props = { + store: Store; + onClose: () => void; + open: boolean; + discussion: Discussion; + isMobile: boolean; +}; + +type State = { + name: string; + memberIds: string[]; + disabled: boolean; + discussionId: string; + notificationType: string; +}; + +class EditDiscussionForm extends React.Component { + constructor(props) { + super(props); + + this.state = { + name: '', + memberIds: [], + disabled: false, + discussionId: '', + notificationType: 'default', + }; + } + + public static getDerivedStateFromProps(props: Props, state: State) { + const { discussion } = props; + + if (state.discussionId === discussion._id) { + return null; + } + + return { + name: (discussion && discussion.name) || '', + memberIds: (discussion && discussion.memberIds) || [], + discussionId: discussion._id, + notificationType: discussion.notificationType || 'default', + }; + } + + public render() { + const { open, store } = this.props; + const { currentTeam, currentUser } = store; + + const membersMinusCreator = Array.from(currentTeam.members.values()).filter( + (user) => user._id !== currentUser._id, + ); + + // console.log(currentTeam.members); + + return ( + + Edit Discussion + + Edit discussion +
    +
    + { + this.setState({ name: event.target.value }); + }} + /> +
    +

    + +

    +
    + + Notification type + + + Choose how to notify members about new Posts inside Discussion. + + +

    +
    + + + + +

    +
    +
    + ); + } + + public handleMembersChange = (memberIds) => { + this.setState({ memberIds }); + }; + + public handleClose = () => { + this.setState({ name: '', memberIds: [], disabled: false, notificationType: 'default' }); + this.props.onClose(); + }; + + private onSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + const { discussion, store } = this.props; + const { currentTeam } = store; + const { notificationType } = this.state; + + if (!currentTeam) { + notify('Team have not selected'); + return; + } + + const { name, memberIds } = this.state; + + if (!name) { + notify('Please name this Discussion.'); + return; + } + + if (memberIds && !memberIds.includes(discussion.store.currentUser._id)) { + memberIds.push(discussion.store.currentUser._id); + } + + if (!memberIds || memberIds.length < 1) { + notify('Please assign at least one person to this Discussion.'); + return; + } + + if (!notificationType) { + notify('Please select notification type.'); + return; + } + + NProgress.start(); + try { + await discussion.editDiscussion({ name, memberIds, notificationType }); + + this.setState({ name: '', memberIds: [], disabled: false, notificationType: 'default' }); + notify('You successfully edited Discussion.'); + } catch (error) { + console.log(error); + notify(error); + } finally { + this.setState({ disabled: false }); + NProgress.done(); + + this.props.onClose(); + } + }; +} + +export default observer(EditDiscussionForm); diff --git a/book/11-begin/app/components/layout/index.tsx b/book/11-begin/app/components/layout/index.tsx new file mode 100644 index 00000000..509301ce --- /dev/null +++ b/book/11-begin/app/components/layout/index.tsx @@ -0,0 +1,268 @@ +import Avatar from '@material-ui/core/Avatar'; +import Button from '@material-ui/core/Button'; +import Grid from '@material-ui/core/Grid'; +import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; +import LensIcon from '@material-ui/icons/Lens'; + +import Link from 'next/link'; +import React from 'react'; + +import MenuWithLinks from '../common/MenuWithLinks'; +import Confirmer from '../common/Confirmer'; +import Notifier from '../common/Notifier'; + +import { Store } from '../../lib/store'; +import DiscussionList from '../discussions/DiscussionList'; + +const dev = process.env.NODE_ENV !== 'production'; + +const styleGrid = { + width: '100vw', + minHeight: '100vh', + maxWidth: '100%', + padding: '0px 10px', +}; + +const styleGridIsMobile = { + width: '100vw', + minHeight: '100vh', + maxWidth: '100%', + padding: '0px 0px 0px 10px', +}; + +function LayoutWrapper({ + children, + isMobile, + firstGridItem, + store, + isThemeDark, +}: { + children: React.ReactNode; + isMobile: boolean; + firstGridItem: boolean; + store: Store; + isThemeDark: boolean; +}) { + return ( + + + {firstGridItem ? ( + +
    + + + + + + + + +
    +
    +

    +

    + + + ) : null} + + {children} + + + + + ); +} + +type Props = { + children: React.ReactNode; + isMobile?: boolean; + firstGridItem?: boolean; + store?: Store; + teamRequired?: boolean; +}; + +class Layout extends React.Component { + public render() { + const { children, isMobile, firstGridItem, store, teamRequired } = this.props; + + const { currentUser, currentTeam } = store; + + const isThemeDark = currentUser && currentUser.darkTheme === true; + + // console.log(this.props.store.currentUser.darkTheme); + + // const isThemeDark = false; + + // console.log(isMobile); + + // console.log(currentTeam); + + if (!currentUser) { + return ( + + + {children} + + + ); + } + + if (!currentTeam) { + if (teamRequired) { + return ( + + +

    + Select existing team or create a new team. +

    + + + +

    +
    + + ); + } else { + console.log('team not required'); + return ( + + + {children} + + + ); + } + } + + return ( + + +
    + {isMobile || store.currentUrl.includes('create-team') ? null : ( + + { + await store.currentUser.toggleTheme(!store.currentUser.darkTheme); + }} + /> + + )} +
    +
    + {children} + + + ); + } +} + +export default Layout; diff --git a/book/11-begin/app/components/posts/PostContent.tsx b/book/11-begin/app/components/posts/PostContent.tsx new file mode 100644 index 00000000..2eb4ed3c --- /dev/null +++ b/book/11-begin/app/components/posts/PostContent.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +type Props = { html: string }; + +class PostContent extends React.Component { + public render() { + const { html } = this.props; + + return ( +
    + ); + } +} + +export default PostContent; diff --git a/book/11-begin/app/components/posts/PostDetail.tsx b/book/11-begin/app/components/posts/PostDetail.tsx new file mode 100644 index 00000000..3248aa08 --- /dev/null +++ b/book/11-begin/app/components/posts/PostDetail.tsx @@ -0,0 +1,192 @@ +import Avatar from '@material-ui/core/Avatar'; +import Paper from '@material-ui/core/Paper'; +import Tooltip from '@material-ui/core/Tooltip'; +import { observer } from 'mobx-react'; +import moment from 'moment'; +import React from 'react'; + +import confirm from '../../lib/confirm'; +import notify from '../../lib/notify'; +import { Store } from '../../lib/store'; +import { Post } from '../../lib/store/post'; +import { User } from '../../lib/store/user'; + +import MenuWithMenuItems from '../common/MenuWithMenuItems'; + +import PostContent from './PostContent'; + +const stylePaper = { + margin: '10px 0px', + padding: '20px', +}; + +const styleLineSeparator = { + verticalAlign: 'text-bottom', + fontWeight: 300, + fontSize: '16px', + margin: '0px 5px', + opacity: 0.75, +}; + +const getMenuOptions = (post) => ({ + dataId: post._id, + id: `post-menu-${post._id}`, +}); + +const getMenuItemOptions = (post: Post, currentUser: User, component) => { + const items = []; + + if (post.createdUserId !== currentUser._id) { + items.push({ + text: 'Show Markdown', + dataId: post._id, + onClick: component.showMarkdown, + }); + } + + if (post.createdUserId === currentUser._id) { + const isFirstPost = post.discussion.posts.indexOf(post) === 0; + + items.push({ + text: 'Edit', + dataId: post._id, + onClick: component.editPost, + }); + + if (!isFirstPost) { + items.push({ + text: 'Delete', + dataId: post._id, + onClick: component.deletePost, + }); + } + } + + return items; +}; + +type Props = { + post: Post; + store: Store; + isMobile: boolean; + onEditClick: (post) => void; + onShowMarkdownClick: (post) => void; +}; + +class PostDetail extends React.Component { + public render() { + const { post, isMobile } = this.props; + + return {this.renderPostDetail(post, isMobile)}; + } + + public renderPostDetail(post: Post, isMobile) { + const createdDate = moment(post.createdAt).local().format('MMM Do YYYY'); + const lastUpdatedDate = moment(post.lastUpdatedAt).fromNow(); + + return ( + +
    + {this.renderMenu()} +
    +
    + {post.user && ( + + + + )} +
    + + {`By: ${post.user && post.user.displayName}` || 'User'} + | + {`Created: ${post.createdAt && createdDate}` || ''} + + {post.isEdited ? ( + + | + Last edited: {lastUpdatedDate} + + ) : null} + + + +
    +
    +
    + ); + } + + public renderMenu() { + const { post, store } = this.props; + const { currentUser } = store; + + if (!post.user || !currentUser) { + return null; + } + + return ( + + ); + } + + public showMarkdown = () => { + const { post, onShowMarkdownClick } = this.props; + if (onShowMarkdownClick) { + onShowMarkdownClick(post); + } + }; + + public editPost = () => { + const { post, onEditClick } = this.props; + if (onEditClick) { + onEditClick(post); + } + console.log(`PostDetail: ${post._id}`); + }; + + public deletePost = () => { + confirm({ + title: 'Are you sure?', + message: '', + onAnswer: async (answer) => { + if (answer) { + const { post } = this.props; + await post.discussion.deletePost(post); + notify('You successfully deleted Post.'); + } + }, + }); + }; +} + +export default observer(PostDetail); diff --git a/book/11-begin/app/components/posts/PostEditor.tsx b/book/11-begin/app/components/posts/PostEditor.tsx new file mode 100644 index 00000000..3ce06e9f --- /dev/null +++ b/book/11-begin/app/components/posts/PostEditor.tsx @@ -0,0 +1,303 @@ +import Avatar from '@material-ui/core/Avatar'; +import Button from '@material-ui/core/Button'; +import InsertPhotoIcon from '@material-ui/icons/InsertPhoto'; +import he from 'he'; +import marked from 'marked'; +import { observer } from 'mobx-react'; +import NProgress from 'nprogress'; +import React from 'react'; +import { Mention, MentionsInput } from 'react-mentions'; + +import { + getSignedRequestForUploadApiMethod, + uploadFileUsingSignedPutRequestApiMethod, +} from '../../lib/api/team-member'; +import notify from '../../lib/notify'; +import { resizeImage } from '../../lib/resizeImage'; +import { Store } from '../../lib/store'; +import { User } from '../../lib/store/user'; + +import PostContent from './PostContent'; + +function getImageDimension(file): Promise<{ width: number; height: number }> { + const reader = new FileReader(); + const img = new Image(); + + return new Promise((resolve) => { + reader.readAsDataURL(file); + + reader.onload = (e) => { + img.onload = () => { + resolve({ width: img.width, height: img.height }); + }; + + img.src = e.target.result.toString(); + }; + }); +} + +type Props = { + store: Store; + onChanged: (content) => void; + content: string; + members: User[]; + textareaHeight?: string; + placeholder?: string; +}; + +type State = { htmlContent: string }; + +class PostEditor extends React.Component { + constructor(props) { + super(props); + + this.state = { + htmlContent: '', + }; + } + + public render() { + const { htmlContent } = this.state; + const { content, members, store } = this.props; + const { currentUser } = store; + + const membersMinusCurrentUser = members.filter((member) => member._id !== currentUser._id); + + const isThemeDark = store && store.currentUser && store.currentUser.darkTheme === true; + const textareaBackgroundColor = isThemeDark ? '#0d1117' : '#fff'; + + return ( +
    +
    + {' '} + +
    + +
    + + { + const file = event.target.files[0]; + event.target.value = ''; + this.uploadFile(file); + }} + /> +
    +
    +
    + {htmlContent ? ( + + ) : ( + { + this.props.onChanged(event.target.value); + }} + > + ({ + id: u.avatarUrl, + display: u.displayName, + // you: u._id === currentUser._id ? true : false, + }))} + markup={'[`@#__display__`](__id__)'} + displayTransform={(_, display) => { + return `@${display}`; + }} + renderSuggestion={(suggestion) => ( + + + {suggestion.display} + + )} + /> + + )} +
    +
    + ); + } + + public showMarkdownContent = () => { + this.setState({ htmlContent: '' }); + }; + + public showHtmlContent = async () => { + const { content } = this.props; + + function markdownToHtml(postContent) { + const renderer = new marked.Renderer(); + + renderer.link = (href, title, text) => { + const t = title ? ` title="${title}"` : ''; + + if (text.startsWith('@#')) { + return `${text.replace('@#', '@')} `; + } + + return ` + + ${text} + + `; + }; + + marked.setOptions({ + renderer, + breaks: true, + }); + + return marked(he.decode(postContent)); + } + + const htmlContent = content ? markdownToHtml(content) : 'Nothing to preview.'; + this.setState({ htmlContent }); + }; + + private uploadFile = async (file: File) => { + if (!file) { + notify('No file selected.'); + return; + } + + if (!file.type || (!file.type.startsWith('image/') && file.type !== 'application/pdf')) { + notify('Wrong file.'); + return; + } + + const { store } = this.props; + const { currentTeam } = store; + + NProgress.start(); + + const bucket = process.env.BUCKET_FOR_POSTS; + const prefix = `${currentTeam.slug}`; + const fileName = file.name; + const fileType = file.type; + + try { + const responseFromApiServerForUpload = await getSignedRequestForUploadApiMethod({ + fileName, + fileType, + prefix, + bucket, + }); + + let imageMarkdown; + let fileUrl; + + if (file.type.startsWith('image/')) { + const { width } = await getImageDimension(file); + const resizedFile = await resizeImage(file, 1024, 1024); + + await uploadFileUsingSignedPutRequestApiMethod( + resizedFile, + responseFromApiServerForUpload.signedRequest, + ); + + fileUrl = responseFromApiServerForUpload.url; + + console.log(fileUrl); + + const finalWidth = width > 768 ? '100%' : `${width}px`; + + imageMarkdown = ` +
    + Async +
    `; + } else { + await uploadFileUsingSignedPutRequestApiMethod( + file, + responseFromApiServerForUpload.signedRequest, + ); + + fileUrl = responseFromApiServerForUpload.url; + imageMarkdown = `[${file.name}](${fileUrl})`; + } + + const content = `${this.props.content}\n${imageMarkdown.replace(/\s+/g, ' ')}`; + + this.props.onChanged(content); + + NProgress.done(); + notify('You successfully uploaded file.'); + } catch (error) { + console.log(error); + notify(error); + } finally { + NProgress.done(); + } + }; +} + +export default observer(PostEditor); diff --git a/book/11-begin/app/components/posts/PostForm.tsx b/book/11-begin/app/components/posts/PostForm.tsx new file mode 100644 index 00000000..10a0dea9 --- /dev/null +++ b/book/11-begin/app/components/posts/PostForm.tsx @@ -0,0 +1,225 @@ +import Button from '@material-ui/core/Button'; +import he from 'he'; +import marked from 'marked'; +import { observer } from 'mobx-react'; +import NProgress from 'nprogress'; +import React from 'react'; + +import notify from '../../lib/notify'; +import { Store } from '../../lib/store'; +import { Discussion } from '../../lib/store/discussion'; +import { Post } from '../../lib/store/post'; +import { User } from '../../lib/store/user'; + +import PostEditor from './PostEditor'; + +const dev = process.env.NODE_ENV !== 'production'; + +type Props = { + store: Store; + isMobile: boolean; + members: User[]; + post: Post; + discussion: Discussion; + showMarkdownToNonCreator?: boolean; + onFinished?: () => void; +}; + +type State = { + postId: string; + content: string; + disabled: boolean; +}; + +class PostForm extends React.Component { + constructor(props) { + super(props); + + this.state = { + postId: null, + content: '', + disabled: false, + }; + } + + public static getDerivedStateFromProps(props: Props, state: State) { + const { post } = props; + + if (!post && !state.postId) { + return null; + } + + if (post && post._id === state.postId) { + return null; + } + + return { + postId: (post && post._id) || null, + content: (post && post.content) || '', + }; + } + + public render() { + const { store, members, post, isMobile, showMarkdownToNonCreator } = this.props; + const isEditingPost = !!post; + + let title = 'Add Post'; + if (showMarkdownToNonCreator) { + title = 'Showing Markdown'; + } else if (isEditingPost) { + title = 'Edit Post'; + } + + return ( +
    +

    +
    +

    {title}

    +
    +

    +
    +

    + {showMarkdownToNonCreator ? null : ( + + + {isMobile ?

    : null} + + )} + {isEditingPost ? ( + + ) : null} +

    +

    +
    + +

    +

    + {isEditingPost ? ( + + ) : null} +
    +

    +
    +

    +
    + ); + } + + private onSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + const { content } = this.state; + const htmlContent = marked(he.decode(content)); + const { post, onFinished, store, discussion } = this.props; + const isEditingPost = !!post; + + if (!content) { + notify('Add content to your Post'); + return; + } + + if (isEditingPost) { + this.setState({ disabled: true }); + NProgress.start(); + try { + await post.editPost({ content, htmlContent }); + notify('You successfully edited Post'); + } catch (error) { + console.log(error); + notify(error); + } finally { + this.setState({ disabled: false }); + NProgress.done(); + } + + if (onFinished) { + onFinished(); + } + + return; + } + + const { currentTeam } = store; + if (!currentTeam) { + notify('Team is not selected or does not exist.'); + return; + } + + NProgress.start(); + this.setState({ disabled: true }); + + try { + const post = await discussion.addPost(content); + + if (discussion.notificationType === 'email') { + const userIdsForLambda = discussion.memberIds.filter((m) => m !== store.currentUser._id); + + await discussion.sendDataToLambda({ + discussionName: discussion.name, + discussionLink: `${dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP}/teams/${ + discussion.team.slug + }/discussions/${discussion.slug}`, + postContent: post.content, + authorName: post.user.displayName, + userIds: userIdsForLambda, + }); + } + + this.setState({ content: '' }); + + notify('You successfully published new Post.'); + } catch (error) { + console.log(error); + notify(error); + } finally { + this.setState({ disabled: false }); + NProgress.done(); + } + + if (onFinished) { + onFinished(); + } + }; + + private onContentChanged = (content: string) => { + this.setState({ content }); + }; + + private closeForm = () => { + this.setState({ postId: null, content: '' }); + + const { onFinished } = this.props; + if (onFinished) { + onFinished(); + } + }; +} + +export default observer(PostForm); diff --git a/book/11-begin/app/components/teams/InviteMember.tsx b/book/11-begin/app/components/teams/InviteMember.tsx new file mode 100644 index 00000000..981dc55a --- /dev/null +++ b/book/11-begin/app/components/teams/InviteMember.tsx @@ -0,0 +1,110 @@ +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import TextField from '@material-ui/core/TextField'; +import { inject, observer } from 'mobx-react'; +import NProgress from 'nprogress'; +import React from 'react'; + +import notify from '../../lib/notify'; +import { Store } from '../../lib/store'; + +type Props = { + store: Store; + onClose: () => void; + open: boolean; +}; + +type State = { + email: string; + disabled: boolean; +}; + +class InviteMember extends React.Component { + constructor(props) { + super(props); + + this.state = { + email: '', + disabled: false, + }; + } + + public render() { + const { open } = this.props; + + return ( + + Invite member + +
    + { + this.setState({ email: event.target.value }); + }} + /> +

    +
    + {' '} + +

    +
    +
    + ); + } + + private handleClose = () => { + this.setState({ email: '', disabled: false }); + this.props.onClose(); + }; + + private onSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + const { store } = this.props; + + if (!store.currentTeam) { + notify('Team have not selected'); + return; + } + + const { email } = this.state; + + if (!email) { + notify('Email is required'); + return; + } + + NProgress.start(); + try { + this.setState({ disabled: true }); + await store.currentTeam.inviteMember(email); + + this.setState({ email: '' }); + notify('You successfully sent invitation.'); + NProgress.done(); + } catch (error) { + console.log(error); + notify(error); + } finally { + this.props.onClose(); + this.setState({ disabled: false }); + NProgress.done(); + } + }; +} + +export default inject('store')(observer(InviteMember)); diff --git a/book/11-begin/app/lib/api/makeQueryString.ts b/book/11-begin/app/lib/api/makeQueryString.ts new file mode 100644 index 00000000..e719ad24 --- /dev/null +++ b/book/11-begin/app/lib/api/makeQueryString.ts @@ -0,0 +1,10 @@ +function makeQueryString(params) { + const query = Object.keys(params) + .filter((k) => !!params[k]) + .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`) + .join('&'); + + return query; +} + +export { makeQueryString }; diff --git a/book/11-begin/app/lib/api/public.ts b/book/11-begin/app/lib/api/public.ts new file mode 100644 index 00000000..25a8135b --- /dev/null +++ b/book/11-begin/app/lib/api/public.ts @@ -0,0 +1,38 @@ +import sendRequestAndGetResponse from './sendRequestAndGetResponse'; + +const BASE_PATH = '/api/v1/public'; + +export const getUserApiMethod = (request) => + sendRequestAndGetResponse(`${BASE_PATH}/get-user`, { + request, + method: 'GET', + }); + +export const getUserBySlugApiMethod = (slug) => + sendRequestAndGetResponse(`${BASE_PATH}/get-user-by-slug`, { + body: JSON.stringify({ slug }), + }); + +export const emailLoginLinkApiMethod = ({ + email, + invitationToken, +}: { + email: string; + invitationToken?: string; +}) => + sendRequestAndGetResponse('/auth/email-login-link', { + qs: { invitationToken }, + body: JSON.stringify({ user: email }), + }); + +export const acceptAndGetInvitedTeamByTokenApiMethod = (token: string, request) => + sendRequestAndGetResponse(`${BASE_PATH}/invitations/accept-and-get-team-by-token`, { + request, + method: 'GET', + qs: { token }, + }); + +export const removeInvitationIfMemberAddedApiMethod = (token: string) => + sendRequestAndGetResponse(`${BASE_PATH}/invitations/remove-invitation-if-member-added`, { + body: JSON.stringify({ token }), + }); diff --git a/book/11-begin/app/lib/api/sendRequestAndGetResponse.ts b/book/11-begin/app/lib/api/sendRequestAndGetResponse.ts new file mode 100644 index 00000000..2c7194ab --- /dev/null +++ b/book/11-begin/app/lib/api/sendRequestAndGetResponse.ts @@ -0,0 +1,60 @@ +import 'isomorphic-unfetch'; + +import { makeQueryString } from './makeQueryString'; + +const dev = process.env.NODE_ENV !== 'production'; + +export default async function sendRequestAndGetResponse(path, opts: any = {}) { + const headers = Object.assign( + {}, + opts.headers || {}, + opts.externalServer + ? {} + : { + 'Content-type': 'application/json; charset=UTF-8', + }, + ); + + const { request } = opts; + + if (request && request.headers && request.headers.cookie) { + headers.cookie = request.headers.cookie; + } + + // const qs = opts.qs || ''; + + const qs = (opts.qs && `?${makeQueryString(opts.qs)}`) || ''; + + // console.log(`before: ${process.env.URL_API}${path}${qs}`); + + const response = await fetch( + opts.externalServer + ? `${path}${qs}` + : `${dev ? process.env.URL_API : process.env.PRODUCTION_URL_API}${path}${qs}`, + Object.assign({ method: 'POST', credentials: 'include' }, opts, { headers }), + ); + + // console.log(`after: ${process.env.URL_API}${path}${qs}`); + + // console.log(response.status); + // console.log(response.statusText); + + const text = await response.text(); + + if (response.status >= 400) { + console.error(text); + throw new Error(response.status.toString()); + } + + try { + const data = JSON.parse(text); + + return data; + } catch (err) { + if (err instanceof SyntaxError) { + return text; + } + + throw err; + } +} diff --git a/book/11-begin/app/lib/api/team-leader.ts b/book/11-begin/app/lib/api/team-leader.ts new file mode 100644 index 00000000..2593d480 --- /dev/null +++ b/book/11-begin/app/lib/api/team-leader.ts @@ -0,0 +1,44 @@ +import sendRequestAndGetResponse from './sendRequestAndGetResponse'; + +const BASE_PATH = '/api/v1/team-leader'; + +export const addTeamApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/teams/add`, { + body: JSON.stringify(data), + }); + +export const updateTeamApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/teams/update`, { + body: JSON.stringify(data), + }); + +export const getTeamInvitationsApiMethod = (teamId: string) => + sendRequestAndGetResponse(`${BASE_PATH}/teams/get-invitations-for-team`, { + method: 'GET', + qs: { teamId }, + }); + +export const inviteMemberApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/teams/invite-member`, { + body: JSON.stringify(data), + }); + +export const removeMemberApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/teams/remove-member`, { + body: JSON.stringify(data), + }); + +export const fetchCheckoutSessionApiMethod = ({ mode, teamId }: { mode: string; teamId: string }) => + sendRequestAndGetResponse(`${BASE_PATH}/stripe/fetch-checkout-session`, { + body: JSON.stringify({ mode, teamId }), + }); + +export const cancelSubscriptionApiMethod = ({ teamId }: { teamId: string }) => + sendRequestAndGetResponse(`${BASE_PATH}/cancel-subscription`, { + body: JSON.stringify({ teamId }), + }); + +export const getListOfInvoicesApiMethod = () => + sendRequestAndGetResponse(`${BASE_PATH}/get-list-of-invoices-for-customer`, { + method: 'GET', + }); diff --git a/book/11-begin/app/lib/api/team-member.ts b/book/11-begin/app/lib/api/team-member.ts new file mode 100644 index 00000000..24faf72b --- /dev/null +++ b/book/11-begin/app/lib/api/team-member.ts @@ -0,0 +1,98 @@ +import sendRequestAndGetResponse from './sendRequestAndGetResponse'; + +const BASE_PATH = '/api/v1/team-member'; + +export const getSignedRequestForUploadApiMethod = ({ fileName, fileType, prefix, bucket }) => + sendRequestAndGetResponse(`${BASE_PATH}/aws/get-signed-request-for-upload-to-s3`, { + body: JSON.stringify({ fileName, fileType, prefix, bucket }), + }); + +export const uploadFileUsingSignedPutRequestApiMethod = (file, signedRequest, headers = {}) => + sendRequestAndGetResponse(signedRequest, { + externalServer: true, + method: 'PUT', + body: file, + headers, + }); + +export const updateProfileApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/user/update-profile`, { + body: JSON.stringify(data), + }); + +export const toggleThemeApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/user/toggle-theme`, { + body: JSON.stringify(data), + }); + +export const getInitialDataApiMethod = (options: any = {}) => + sendRequestAndGetResponse( + `${BASE_PATH}/get-initial-data`, + Object.assign( + { + body: JSON.stringify(options.data || {}), + }, + options, + ), + ); + +// export const getTeamListApiMethod = () => +// sendRequestAndGetResponse(`${BASE_PATH}/teams`, { +// method: 'GET', +// }); + +export const getTeamMembersApiMethod = (teamId: string) => + sendRequestAndGetResponse(`${BASE_PATH}/teams/get-members`, { + method: 'GET', + qs: { teamId }, + }); + +// Discussion and Post + +export const getDiscussionListApiMethod = (params): Promise<{ discussions: any[] }> => + sendRequestAndGetResponse(`${BASE_PATH}/discussions/list`, { + method: 'GET', + qs: params, + }); + +export const addDiscussionApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/discussions/add`, { + body: JSON.stringify(data), + }); + +export const editDiscussionApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/discussions/edit`, { + body: JSON.stringify(data), + }); + +export const deleteDiscussionApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/discussions/delete`, { + body: JSON.stringify(data), + }); + +export const getPostListApiMethod = (discussionId: string) => + sendRequestAndGetResponse(`${BASE_PATH}/posts/list`, { + method: 'GET', + qs: { discussionId }, + }); + +export const addPostApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/posts/add`, { + body: JSON.stringify(data), + }); + +export const editPostApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/posts/edit`, { + body: JSON.stringify(data), + }); + +export const deletePostApiMethod = (data) => + sendRequestAndGetResponse(`${BASE_PATH}/posts/delete`, { + body: JSON.stringify(data), + }); + +export const sendDataToLambdaApiMethod = (data) => + sendRequestAndGetResponse(`${process.env.API_GATEWAY_ENDPOINT}/`, { + externalServer: true, + body: JSON.stringify(data), + }); diff --git a/book/11-begin/app/lib/confirm.ts b/book/11-begin/app/lib/confirm.ts new file mode 100644 index 00000000..661bfff8 --- /dev/null +++ b/book/11-begin/app/lib/confirm.ts @@ -0,0 +1,13 @@ +import { openConfirmDialogExternal } from '../components/common/Confirmer'; + +export default function confirm({ + title, + message, + onAnswer, +}: { + title: string; + message: string; + onAnswer: (answer) => void; +}) { + openConfirmDialogExternal({ title, message, onAnswer }); +} diff --git a/book/11-begin/app/lib/isMobile.ts b/book/11-begin/app/lib/isMobile.ts new file mode 100644 index 00000000..190b61c4 --- /dev/null +++ b/book/11-begin/app/lib/isMobile.ts @@ -0,0 +1,24 @@ +const mobileRE = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i; + +export function isMobile(opts) { + if (!opts) { + opts = {}; + } + + let ua = opts.ua; + + if (!ua && typeof navigator !== 'undefined') { + ua = navigator.userAgent; + } + + if (!ua && opts.req && opts.req.headers && typeof opts.req.headers['user-agent'] === 'string') { + ua = opts.req.headers['user-agent']; + // console.log(ua); + } + + if (typeof ua !== 'string') { + return false; + } + + return mobileRE.test(ua); +} diff --git a/book/11-begin/app/lib/notify.ts b/book/11-begin/app/lib/notify.ts new file mode 100644 index 00000000..76e2084f --- /dev/null +++ b/book/11-begin/app/lib/notify.ts @@ -0,0 +1,5 @@ +import { openSnackbarExternal } from '../components/common/Notifier'; + +export default function notify(obj) { + openSnackbarExternal({ message: obj.message || obj.toString() }); +} diff --git a/book/11-begin/app/lib/resizeImage.ts b/book/11-begin/app/lib/resizeImage.ts new file mode 100644 index 00000000..97b4221c --- /dev/null +++ b/book/11-begin/app/lib/resizeImage.ts @@ -0,0 +1,59 @@ +function resizeImage(file: File, MAX_WIDTH, MAX_HEIGHT) { + const image = document.createElement('img'); + + const resize = (resolve) => () => { + let isResizeNeeded = false; + let width = image.width; + let height = image.height; + + if (width > height) { + if (width > MAX_WIDTH) { + isResizeNeeded = true; + height *= MAX_WIDTH / width; + width = MAX_WIDTH; + } + } else { + if (height > MAX_HEIGHT) { + isResizeNeeded = true; + width *= MAX_HEIGHT / height; + height = MAX_HEIGHT; + } + } + + if (isResizeNeeded) { + const canvas = document.createElement('canvas'); + + canvas.width = width; + canvas.height = height; + + console.log(width, height); + + const ctx = canvas.getContext('2d'); + ctx.drawImage(image, 0, 0, width, height); + + canvas.toBlob((blob) => { + resolve(blob); + }, file.type); + } else { + resolve(file); + } + }; + + return new Promise((resolve) => { + const reader = new FileReader(); + + console.log(`before ${image.src}`); + + reader.readAsDataURL(file); + + reader.onload = (e) => { + image.src = e.target.result.toString(); + + image.onload = resize(resolve); + + console.log(`after ${image.src}`); + }; + }); +} + +export { resizeImage }; diff --git a/book/11-begin/app/lib/sharedStyles.ts b/book/11-begin/app/lib/sharedStyles.ts new file mode 100644 index 00000000..b1f7441b --- /dev/null +++ b/book/11-begin/app/lib/sharedStyles.ts @@ -0,0 +1,27 @@ +const styleBigAvatar = { + width: '80px', + height: '80px', + margin: '0px auto 15px', +}; + +const styleRaisedButton = { + margin: '15px', +}; + +const styleToolbar = { + background: '#FFF', + height: '64px', + paddingRight: '20px', +}; + +const styleTextField = { + color: '#222', + fontWeight: '300', +}; + +const styleForm = { + margin: '7% auto', + width: '360px', +}; + +export { styleBigAvatar, styleRaisedButton, styleToolbar, styleTextField, styleForm }; diff --git a/book/11-begin/app/lib/store/discussion.ts b/book/11-begin/app/lib/store/discussion.ts new file mode 100644 index 00000000..d028d30d --- /dev/null +++ b/book/11-begin/app/lib/store/discussion.ts @@ -0,0 +1,264 @@ +import { action, IObservableArray, observable, runInAction, computed, makeObservable } from 'mobx'; + +import { + addPostApiMethod, + deletePostApiMethod, + editDiscussionApiMethod, + getPostListApiMethod, + sendDataToLambdaApiMethod, +} from '../api/team-member'; +import { Store } from './index'; +import { Team } from './team'; +import { Post } from './post'; + +class Discussion { + public _id: string; + public createdUserId: string; + public store: Store; + public team: Team; + + public name: string; + public slug: string; + public memberIds: IObservableArray = observable([]); + public posts: IObservableArray = observable([]); + public isLoadingPosts = false; + + public notificationType: string; + + constructor(params) { + makeObservable(this, { + name: observable, + slug: observable, + memberIds: observable, + posts: observable, + isLoadingPosts: observable, + + editDiscussion: action, + changeLocalCache: action, + + setInitialPosts: action, + loadPosts: action, + addPost: action, + addPostToLocalCache: action, + deletePost: action, + + addDiscussionToLocalCache: action, + editDiscussionFromLocalCache: action, + deleteDiscussionFromLocalCache: action, + editPostFromLocalCache: action, + deletePostFromLocalCache: action, + + members: computed, + }); + + this._id = params._id; + this.createdUserId = params.createdUserId; + this.store = params.store; + this.team = params.team; + + this.name = params.name; + this.slug = params.slug; + this.memberIds.replace(params.memberIds || []); + + this.notificationType = params.notificationType; + + if (params.initialPosts) { + this.setInitialPosts(params.initialPosts); + console.log(params.initialPosts[0]); + } else { + this.loadPosts(); + } + } + + public async editDiscussion(data) { + try { + await editDiscussionApiMethod({ + id: this._id, + ...data, + socketId: (this.store.socket && this.store.socket.id) || null, + }); + + runInAction(() => { + this.changeLocalCache(data); + }); + } catch (error) { + console.error(error); + throw error; + } + } + + public changeLocalCache(data) { + this.name = data.name; + this.memberIds.replace(data.memberIds || []); + } + + get members() { + return this.memberIds.map((id) => this.team.members.get(id)).filter((u) => !!u); + } + + public setInitialPosts(posts) { + const postObjs = posts.map((p) => new Post({ discussion: this, store: this.store, ...p })); + this.posts.replace(postObjs); + } + + public async loadPosts() { + if (this.store.isServer || this.isLoadingPosts) { + return; + } + + this.isLoadingPosts = true; + + try { + const { posts = [] } = await getPostListApiMethod(this._id); + + runInAction(() => { + const postObjs = posts.map((t) => new Post({ discussion: this, store: this.store, ...t })); + this.posts.replace(postObjs); + }); + } finally { + runInAction(() => { + this.isLoadingPosts = false; + }); + } + } + + public async addPost(content: string): Promise { + const { post } = await addPostApiMethod({ + discussionId: this._id, + content, + socketId: (this.store.socket && this.store.socket.id) || null, + }); + + return new Promise((resolve) => { + runInAction(() => { + const obj = this.addPostToLocalCache(post); + resolve(obj); + }); + }); + } + + public addPostToLocalCache(data) { + const postObj = new Post({ discussion: this, store: this.store, ...data }); + + this.posts.push(postObj); + + return postObj; + } + + public async deletePost(post: Post) { + await deletePostApiMethod({ + id: post._id, + discussionId: this._id, + socketId: (this.store.socket && this.store.socket.id) || null, + }); + + runInAction(() => { + this.posts.remove(post); + }); + } + + public joinSocketRooms() { + if (this.store.socket) { + console.log('joining socket discussion room', this.name); + this.store.socket.emit('joinTeamRoom', this.team._id); + this.store.socket.emit('joinDiscussionRoom', this._id); + } + } + + public leaveSocketRooms() { + if (this.store.socket) { + console.log('leaving socket discussion room', this.name); + this.store.socket.emit('leaveTeamRoom', this.team._id); + this.store.socket.emit('leaveDiscussionRoom', this._id); + } + } + + public handleDiscussionRealtimeEvent = (data) => { + console.log('discussion realtime event', data); + const { actionType } = data; + + if (actionType === 'added') { + this.addDiscussionToLocalCache(data.discussion); + } else if (actionType === 'edited') { + this.editDiscussionFromLocalCache(data.discussion); + } else if (actionType === 'deleted') { + this.deleteDiscussionFromLocalCache(data.id); + } + }; + + public addDiscussionToLocalCache(data): Discussion { + const obj = new Discussion({ team: this.team, store: this.store, ...data }); + + if (obj.memberIds.includes(this.store.currentUser._id)) { + this.team.discussions.push(obj); + } + + return obj; + } + + public editDiscussionFromLocalCache(data) { + const discussion = this.team.discussions.find((item) => item._id === data._id); + if (discussion) { + if (data.memberIds && data.memberIds.includes(this.store.currentUser._id)) { + discussion.changeLocalCache(data); + } else { + this.deleteDiscussionFromLocalCache(data._id); + } + } else if (data.memberIds && data.memberIds.includes(this.store.currentUser._id)) { + this.addDiscussionToLocalCache(data); + } + } + + public deleteDiscussionFromLocalCache(discussionId: string) { + const discussion = this.team.discussions.find((item) => item._id === discussionId); + this.team.discussions.remove(discussion); + } + + public handlePostRealtimeEvent(data) { + const { actionType } = data; + + if (actionType === 'added') { + this.addPostToLocalCache(data.post); + } else if (actionType === 'edited') { + this.editPostFromLocalCache(data.post); + } else if (actionType === 'deleted') { + this.deletePostFromLocalCache(data.id); + } + } + + public editPostFromLocalCache(data) { + const post = this.posts.find((t) => t._id === data._id); + if (post) { + post.changeLocalCache(data); + } + } + + public deletePostFromLocalCache(postId) { + const post = this.posts.find((t) => t._id === postId); + this.posts.remove(post); + } + + public async sendDataToLambda({ + discussionName, + discussionLink, + postContent, + authorName, + userIds, + }) { + console.log(discussionName, discussionLink, authorName, postContent, userIds); + try { + await sendDataToLambdaApiMethod({ + discussionName, + discussionLink, + postContent, + authorName, + userIds, + }); + } catch (error) { + console.error(error); + throw error; + } + } +} + +export { Discussion }; diff --git a/book/11-begin/app/lib/store/index.ts b/book/11-begin/app/lib/store/index.ts new file mode 100644 index 00000000..ec15e094 --- /dev/null +++ b/book/11-begin/app/lib/store/index.ts @@ -0,0 +1,186 @@ +import { action, configure, IObservableArray, observable, makeObservable } from 'mobx'; +import { useStaticRendering } from 'mobx-react'; +// @ts-expect-error no exported member io socket.io-client +import { io } from 'socket.io-client'; + +import { addTeamApiMethod, getTeamInvitationsApiMethod } from '../api/team-leader'; +import { getTeamMembersApiMethod } from '../api/team-member'; + +import { User } from './user'; +import { Team } from './team'; + +const dev = process.env.NODE_ENV !== 'production'; + +useStaticRendering(typeof window === 'undefined'); + +configure({ enforceActions: 'observed' }); + +class Store { + public isServer: boolean; + + public currentUser?: User = null; + public currentUrl = ''; + + public currentTeam?: Team = null; + + public teams: IObservableArray = observable([]); + + public socket: SocketIOClient.Socket; + + constructor({ + initialState = {}, + isServer, + socket = null, + }: { + initialState?: any; + isServer: boolean; + socket?: SocketIOClient.Socket; + }) { + makeObservable(this, { + currentUser: observable, + currentUrl: observable, + currentTeam: observable, + + changeCurrentUrl: action, + setCurrentUser: action, + setCurrentTeam: action, + }); + + this.isServer = !!isServer; + + this.setCurrentUser(initialState.user); + + this.currentUrl = initialState.currentUrl || ''; + + // console.log(initialState.user); + + // if (initialState.teamSlug || (initialState.user && initialState.user.defaultTeamSlug)) { + // this.setCurrentTeam( + // initialState.teamSlug || initialState.user.defaultTeamSlug, + // initialState.teams, + // ); + // } + + // console.log(initialState.team); + + this.setCurrentTeam(initialState.team); + + this.socket = socket; + + if (socket) { + socket.on('disconnect', () => { + console.log('socket: ## disconnected'); + }); + + socket.on('reconnect', (attemptNumber) => { + console.log('socket: $$ reconnected', attemptNumber); + }); + } + } + + public changeCurrentUrl(url: string) { + this.currentUrl = url; + } + + public async setCurrentUser(user) { + if (user) { + this.currentUser = new User({ store: this, ...user }); + } else { + this.currentUser = null; + } + } + + public async addTeam({ name, avatarUrl }: { name: string; avatarUrl: string }): Promise { + const data = await addTeamApiMethod({ name, avatarUrl }); + const team = new Team({ store: this, ...data }); + + return team; + } + + public async setCurrentTeam(team) { + if (this.currentTeam) { + if (this.currentTeam.slug === team.slug) { + return; + } + } + + if (team) { + this.currentTeam = new Team({ ...team, store: this }); + + const users = + team.initialMembers || (await getTeamMembersApiMethod(this.currentTeam._id)).users; + + const invitations = + team.initialInvitations || + (await getTeamInvitationsApiMethod(this.currentTeam._id)).invitations; + + this.currentTeam.setInitialMembersAndInvitations(users, invitations); + } else { + this.currentTeam = null; + } + } + + public addTeamToLocalCache(data): Team { + const teamObj = new Team({ user: this.currentUser, store: this, ...data }); + this.teams.unshift(teamObj); + + return teamObj; + } + + public editTeamFromLocalCache(data) { + const team = this.teams.find((item) => item._id === data._id); + + if (team) { + if (data.memberIds && data.memberIds.includes(this.currentUser._id)) { + team.changeLocalCache(data); + } else { + this.removeTeamFromLocalCache(data._id); + } + } else if (data.memberIds && data.memberIds.includes(this.currentUser._id)) { + this.addTeamToLocalCache(data); + } + } + + public removeTeamFromLocalCache(teamId: string) { + const team = this.teams.find((t) => t._id === teamId); + + this.teams.remove(team); + } +} + +let store: Store = null; + +function initializeStore(initialState = {}) { + const isServer = typeof window === 'undefined'; + + const socket = isServer + ? null + : io(dev ? process.env.URL_API : process.env.PRODUCTION_URL_API, { + reconnection: true, + autoConnect: true, + transports: ['polling', 'websocket'], + withCredentials: true, + }); + + const _store = + store !== null && store !== undefined ? store : new Store({ initialState, isServer, socket }); + + // For SSG and SSR always create a new store + if (typeof window === 'undefined') { + return _store; + } + // Create the store once in the client + if (!store) { + store = _store; + } + + // console.log(_store); + + return _store; +} + +function getStore() { + return store; +} + +export { Store, initializeStore, getStore }; diff --git a/book/11-begin/app/lib/store/invitation.ts b/book/11-begin/app/lib/store/invitation.ts new file mode 100644 index 00000000..64d8a90d --- /dev/null +++ b/book/11-begin/app/lib/store/invitation.ts @@ -0,0 +1,12 @@ +class Invitation { + public _id: string; + public teamId: string; + public email: string; + public createdAt: Date; + + constructor(params) { + Object.assign(this, params); + } +} + +export { Invitation }; diff --git a/book/11-begin/app/lib/store/post.ts b/book/11-begin/app/lib/store/post.ts new file mode 100644 index 00000000..bd446c53 --- /dev/null +++ b/book/11-begin/app/lib/store/post.ts @@ -0,0 +1,79 @@ +import { action, computed, observable, runInAction, makeObservable } from 'mobx'; + +import { editPostApiMethod } from '../api/team-member'; + +import { Store } from './index'; +import { User } from './user'; +import { Discussion } from './discussion'; + +export class Post { + public _id: string; + public createdUserId: string; + public createdAt: Date; + public discussionId: string; + + public discussion: Discussion; + public store: Store; + + public content: string; + public htmlContent: string; + + public isEdited: boolean; + public lastUpdatedAt: Date; + + constructor(params) { + makeObservable(this, { + content: observable, + htmlContent: observable, + isEdited: observable, + lastUpdatedAt: observable, + + editPost: action, + changeLocalCache: action, + + user: computed, + }); + + this._id = params._id; + this.createdUserId = params.createdUserId; + this.createdAt = params.createdAt; + this.discussionId = params.discussionId; + + this.content = params.content; + this.htmlContent = params.htmlContent; + + this.discussion = params.discussion; + this.store = params.store; + + this.isEdited = params.isEdited; + this.lastUpdatedAt = params.lastUpdatedAt; + } + + public async editPost(data) { + try { + await editPostApiMethod({ + id: this._id, + content: data.content, + socketId: (this.store.socket && this.store.socket.id) || null, + }); + + runInAction(() => { + this.changeLocalCache(data); + }); + } catch (error) { + console.error(error); + throw error; + } + } + + public changeLocalCache(data) { + this.content = data.content; + this.htmlContent = data.htmlContent; + this.isEdited = true; + this.lastUpdatedAt = data.lastUpdatedAt; + } + + get user(): User { + return this.discussion.team.members.get(this.createdUserId) || null; + } +} diff --git a/book/11-begin/app/lib/store/team.ts b/book/11-begin/app/lib/store/team.ts new file mode 100644 index 00000000..4c3ca9e4 --- /dev/null +++ b/book/11-begin/app/lib/store/team.ts @@ -0,0 +1,323 @@ +import { action, computed, IObservableArray, observable, runInAction, makeObservable } from 'mobx'; +import Router from 'next/router'; +import { + cancelSubscriptionApiMethod, + inviteMemberApiMethod, + removeMemberApiMethod, + updateTeamApiMethod, +} from '../api/team-leader'; +import { + addDiscussionApiMethod, + deleteDiscussionApiMethod, + getDiscussionListApiMethod, +} from '../api/team-member'; +import { Store } from './index'; +import { User } from './user'; +import { Invitation } from './invitation'; +import { Discussion } from './discussion'; + +class Team { + public store: Store; + + public _id: string; + public teamLeaderId: string; + + public name: string; + public slug: string; + public avatarUrl: string; + public memberIds: IObservableArray = observable([]); + public members: Map = new Map(); + public invitations: Map = new Map(); + + public currentDiscussion?: Discussion; + public currentDiscussionSlug?: string; + public discussions: IObservableArray = observable([]); + public isLoadingDiscussions = false; + + public stripeSubscription: { + id: string; + object: string; + application_fee_percent: number; + billing: string; + cancel_at_period_end: boolean; + billing_cycle_anchor: number; + canceled_at: number; + created: number; + }; + public isSubscriptionActive: boolean; + public isPaymentFailed: boolean; + + constructor(params) { + makeObservable(this, { + name: observable, + slug: observable, + avatarUrl: observable, + memberIds: observable, + members: observable, + invitations: observable, + currentDiscussion: observable, + currentDiscussionSlug: observable, + isLoadingDiscussions: observable, + discussions: observable, + + setInitialMembersAndInvitations: action, + updateTheme: action, + inviteMember: action, + removeMember: action, + setInitialDiscussions: action, + loadDiscussions: action, + addDiscussion: action, + addDiscussionToLocalCache: action, + deleteDiscussion: action, + deleteDiscussionFromLocalCache: action, + getDiscussionBySlug: action, + + orderedDiscussions: computed, + }); + + this._id = params._id; + this.teamLeaderId = params.teamLeaderId; + this.slug = params.slug; + this.name = params.name; + this.avatarUrl = params.avatarUrl; + this.memberIds.replace(params.memberIds || []); + this.currentDiscussionSlug = params.currentDiscussionSlug || null; + + this.stripeSubscription = params.stripeSubscription; + this.isSubscriptionActive = params.isSubscriptionActive; + this.isPaymentFailed = params.isPaymentFailed; + + this.store = params.store; + + if (params.initialDiscussions) { + this.setInitialDiscussions(params.initialDiscussions); + } else { + this.loadDiscussions(); + } + } + + public setInitialMembersAndInvitations(users, invitations) { + this.members.clear(); + this.invitations.clear(); + + for (const user of users) { + if (this.store.currentUser && this.store.currentUser._id === user._id) { + this.members.set(user._id, this.store.currentUser); + } else { + this.members.set(user._id, new User(user)); + } + } + + for (const invitation of invitations) { + this.invitations.set(invitation._id, new Invitation(invitation)); + } + + // console.log(this.members); + } + + public async updateTheme({ name, avatarUrl }: { name: string; avatarUrl: string }) { + try { + const { slug } = await updateTeamApiMethod({ + teamId: this._id, + name, + avatarUrl, + }); + + runInAction(() => { + this.name = name; + this.avatarUrl = avatarUrl; + this.slug = slug; + }); + } catch (error) { + console.error(error); + throw error; + } + } + + public async inviteMember(email: string) { + try { + const { newInvitation } = await inviteMemberApiMethod({ teamId: this._id, email }); + + runInAction(() => { + this.invitations.set(newInvitation._id, new Invitation(newInvitation)); + }); + } catch (error) { + console.error(error); + throw error; + } + } + + public async removeMember(userId: string) { + try { + await removeMemberApiMethod({ teamId: this._id, userId }); + + runInAction(() => { + this.members.delete(userId); + this.memberIds.remove(userId); + }); + } catch (error) { + console.error(error); + throw error; + } + } + + public setCurrentDiscussion({ slug }: { slug: string }) { + this.currentDiscussionSlug = slug; + for (const discussion of this.discussions) { + if (discussion && discussion.slug === slug) { + this.currentDiscussion = discussion; + break; + } + } + } + + public setInitialDiscussions(discussions) { + const discussionObjs = discussions.map( + (d) => new Discussion({ team: this, store: this.store, ...d }), + ); + + this.discussions.replace(discussionObjs); + + if (!this.currentDiscussionSlug && this.discussions.length > 0) { + this.currentDiscussionSlug = this.orderedDiscussions[0].slug; + } + + if (this.currentDiscussionSlug) { + this.setCurrentDiscussion({ slug: this.currentDiscussionSlug }); + } + } + + public async loadDiscussions() { + if (this.store.isServer || this.isLoadingDiscussions) { + return; + } + + this.isLoadingDiscussions = true; + + try { + const { discussions = [] } = await getDiscussionListApiMethod({ + teamId: this._id, + }); + const newList: Discussion[] = []; + + runInAction(() => { + discussions.forEach((d) => { + const disObj = this.discussions.find((obj) => obj._id === d._id); + if (disObj) { + disObj.changeLocalCache(d); + newList.push(disObj); + } else { + newList.push(new Discussion({ team: this, store: this.store, ...d })); + } + }); + + this.discussions.replace(newList); + }); + } finally { + runInAction(() => { + this.isLoadingDiscussions = false; + }); + } + } + + public changeLocalCache(data) { + this.name = data.name; + this.memberIds.replace(data.memberIds || []); + } + + public async addDiscussion(data): Promise { + const { discussion } = await addDiscussionApiMethod({ + teamId: this._id, + socketId: (this.store.socket && this.store.socket.id) || null, + ...data, + }); + + return new Promise((resolve) => { + runInAction(() => { + const obj = this.addDiscussionToLocalCache(discussion); + resolve(obj); + }); + }); + } + + public addDiscussionToLocalCache(data): Discussion { + const obj = new Discussion({ team: this, store: this.store, ...data }); + + if (obj.memberIds.includes(this.store.currentUser._id)) { + this.discussions.push(obj); + } + + return obj; + } + + public async deleteDiscussion(id: string) { + await deleteDiscussionApiMethod({ + id, + socketId: (this.store.socket && this.store.socket.id) || null, + }); + + runInAction(() => { + this.deleteDiscussionFromLocalCache(id); + + const discussion = this.discussions.find((d) => d._id === id); + + if (this.currentDiscussion === discussion) { + this.currentDiscussion = null; + this.currentDiscussionSlug = null; + + if (this.discussions.length > 0) { + const d = this.discussions[0]; + + Router.push( + `/discussion?teamSlug=${this.slug}&discussionSlug=${d.slug}`, + `/teams/${this.slug}/discussions/${d.slug}`, + ); + } else { + Router.push(`/discussion?teamSlug=${this.slug}`, `/teams/${this.slug}/discussions`); + } + } + }); + } + + public deleteDiscussionFromLocalCache(discussionId: string) { + const discussion = this.discussions.find((item) => item._id === discussionId); + this.discussions.remove(discussion); + } + + public getDiscussionBySlug(slug: string): Discussion { + return this.discussions.find((d) => d.slug === slug); + } + + public async cancelSubscription({ teamId }: { teamId: string }) { + try { + const { isSubscriptionActive } = await cancelSubscriptionApiMethod({ teamId }); + + runInAction(() => { + this.isSubscriptionActive = isSubscriptionActive; + }); + } catch (error) { + console.error(error); + throw error; + } + } + + public async checkIfTeamLeaderMustBeCustomer() { + let ifTeamLeaderMustBeCustomerOnClient: boolean; + + if (this && this.memberIds.length < 2) { + ifTeamLeaderMustBeCustomerOnClient = false; + } else if (this && this.memberIds.length >= 2 && this.isSubscriptionActive) { + ifTeamLeaderMustBeCustomerOnClient = false; + } else if (this && this.memberIds.length >= 2 && !this.isSubscriptionActive) { + ifTeamLeaderMustBeCustomerOnClient = true; + } + + return ifTeamLeaderMustBeCustomerOnClient; + } + + get orderedDiscussions() { + return this.discussions.slice().sort(); + } +} + +export { Team }; diff --git a/book/11-begin/app/lib/store/user.ts b/book/11-begin/app/lib/store/user.ts new file mode 100644 index 00000000..8d45a5c9 --- /dev/null +++ b/book/11-begin/app/lib/store/user.ts @@ -0,0 +1,110 @@ +import { action, observable, runInAction, makeObservable } from 'mobx'; + +import * as NProgress from 'nprogress'; + +import { getListOfInvoicesApiMethod } from '../api/team-leader'; +import { toggleThemeApiMethod, updateProfileApiMethod } from '../api/team-member'; +import { Store } from './index'; + +class User { + public store: Store; + + public _id: string; + public slug: string; + public email: string | null; + public displayName: string | null; + public avatarUrl: string | null; + public isSignedupViaGoogle: boolean; + + public darkTheme = false; + public defaultTeamSlug: string; + + public stripeCard: { + brand: string; + funding: string; + last4: string; + exp_month: number; + exp_year: number; + }; + public hasCardInformation: boolean; + public stripeListOfInvoices: { + object: string; + data: [ + { + amount_paid: number; + teamName: string; + created: number; + hosted_invoice_url: string; + }, + ]; + has_more: boolean; + }; + + constructor(params) { + makeObservable(this, { + slug: observable, + email: observable, + displayName: observable, + avatarUrl: observable, + // darkTheme: observable, + defaultTeamSlug: observable, + stripeCard: observable, + stripeListOfInvoices: observable, + + updateProfile: action, + toggleTheme: action, + getListOfInvoices: action, + }); + + this.store = params.store; + this._id = params._id; + this.slug = params.slug; + this.email = params.email; + this.displayName = params.displayName; + this.avatarUrl = params.avatarUrl; + this.isSignedupViaGoogle = !!params.isSignedupViaGoogle; + this.darkTheme = !!params.darkTheme; + this.defaultTeamSlug = params.defaultTeamSlug; + + this.stripeCard = params.stripeCard; + this.hasCardInformation = params.hasCardInformation; + this.stripeListOfInvoices = params.stripeListOfInvoices; + } + + public async updateProfile({ name, avatarUrl }: { name: string; avatarUrl: string }) { + const { updatedUser } = await updateProfileApiMethod({ + name, + avatarUrl, + }); + + runInAction(() => { + this.displayName = updatedUser.displayName; + this.avatarUrl = updatedUser.avatarUrl; + this.slug = updatedUser.slug; + }); + } + + public async toggleTheme(darkTheme: boolean) { + await toggleThemeApiMethod({ darkTheme }); + runInAction(() => { + this.darkTheme = darkTheme; + }); + NProgress.start(); + NProgress.set(0.5); + window.location.reload(); + } + + public async getListOfInvoices() { + try { + const { stripeListOfInvoices } = await getListOfInvoicesApiMethod(); + runInAction(() => { + this.stripeListOfInvoices = stripeListOfInvoices; + }); + } catch (error) { + console.error(error); + throw error; + } + } +} + +export { User }; diff --git a/book/11-begin/app/lib/theme.ts b/book/11-begin/app/lib/theme.ts new file mode 100644 index 00000000..68f4b746 --- /dev/null +++ b/book/11-begin/app/lib/theme.ts @@ -0,0 +1,39 @@ +import { createMuiTheme } from '@material-ui/core/styles'; + +const themeDark = createMuiTheme({ + palette: { + primary: { main: '#238636' }, + secondary: { main: '#b62324' }, + type: 'dark', + background: { default: '#0d1117' }, + text: { + primary: '#c9d1d9', + }, + }, + typography: { + fontFamily: ['IBM Plex Mono', 'monospace'].join(','), + button: { + textTransform: 'none', + }, + }, +}); + +const themeLight = createMuiTheme({ + palette: { + primary: { main: '#238636' }, + secondary: { main: '#b62324' }, + type: 'light', + background: { default: '#fff' }, + text: { + primary: '#222', + }, + }, + typography: { + fontFamily: ['IBM Plex Mono', 'monospace'].join(','), + button: { + textTransform: 'none', + }, + }, +}); + +export { themeDark, themeLight }; diff --git a/book/11-begin/app/lib/withAuth.tsx b/book/11-begin/app/lib/withAuth.tsx new file mode 100644 index 00000000..fe85ec0b --- /dev/null +++ b/book/11-begin/app/lib/withAuth.tsx @@ -0,0 +1,94 @@ +import { observer } from 'mobx-react'; +import Router from 'next/router'; +import React from 'react'; + +import * as NProgress from 'nprogress'; + +import { Store, getStore } from './store'; + +Router.events.on('routeChangeStart', () => { + NProgress.start(); +}); + +Router.events.on('routeChangeComplete', (url) => { + const store = getStore(); + if (store) { + store.changeCurrentUrl(url); + } + + if (window && process.env.GA_MEASUREMENT_ID) { + (window as any).gtag('config', process.env.GA_MEASUREMENT_ID, { + page_path: url, + }); + } + + NProgress.done(); +}); + +Router.events.on('routeChangeError', () => NProgress.done()); + +export default function withAuth(Component, { loginRequired = true, logoutRequired = false } = {}) { + class WithAuth extends React.Component<{ store: Store }> { + public static async getInitialProps(ctx) { + console.log('WithAuth.getInitialProps'); + + const { req } = ctx; + + let pageComponentProps = {}; + + if (Component.getInitialProps) { + pageComponentProps = await Component.getInitialProps(ctx); + } + + return { + ...pageComponentProps, + isServer: !!req, + }; + } + + public componentDidMount() { + console.log('WithAuth.componentDidMount'); + + const { store } = this.props; + const user = store.currentUser; + + if (loginRequired && !logoutRequired && !user) { + Router.push('/login'); + return; + } + + let redirectUrl = '/login'; + let asUrl = '/login'; + if (user) { + if (!user.defaultTeamSlug) { + redirectUrl = '/create-team'; + asUrl = '/create-team'; + } else { + redirectUrl = `/your-settings`; + asUrl = `/your-settings`; + } + } + + if (logoutRequired && user) { + Router.push(redirectUrl, asUrl); + } + } + + public render() { + const { store } = this.props; + const user = store.currentUser; + + if (loginRequired && !logoutRequired && !user) { + return null; + } + + if (logoutRequired && user) { + return null; + } + + return ; + } + } + + return observer(WithAuth); +} diff --git a/book/11-begin/app/next-env.d.ts b/book/11-begin/app/next-env.d.ts new file mode 100644 index 00000000..7b7aa2c7 --- /dev/null +++ b/book/11-begin/app/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/book/11-begin/app/next.config.js b/book/11-begin/app/next.config.js new file mode 100644 index 00000000..a5eed999 --- /dev/null +++ b/book/11-begin/app/next.config.js @@ -0,0 +1,18 @@ +// eslint-disable-next-line +require('dotenv').config(); + +module.exports = { + env: { + URL_APP: process.env.URL_APP, + PORT_APP: process.env.PORT_APP, + URL_API: process.env.URL_API, + BUCKET_FOR_AVATARS: process.env.BUCKET_FOR_AVATARS, + BUCKET_FOR_TEAM_LOGOS: process.env.BUCKET_FOR_TEAM_LOGOS, + STRIPE_TEST_PUBLISHABLEKEY: process.env.STRIPE_TEST_PUBLISHABLEKEY, + STRIPE_LIVE_PUBLISHABLEKEY: process.env.STRIPE_LIVE_PUBLISHABLEKEY, + API_GATEWAY_ENDPOINT: process.env.API_GATEWAY_ENDPOINT, + PRODUCTION_URL_API: process.env.PRODUCTION_URL_API, + PRODUCTION_URL_APP: process.env.PRODUCTION_URL_APP, + GA_MEASUREMENT_ID: process.env.GA_MEASUREMENT_ID, + }, +}; diff --git a/book/11-begin/app/nodemon.json b/book/11-begin/app/nodemon.json new file mode 100644 index 00000000..61838926 --- /dev/null +++ b/book/11-begin/app/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["server"], + "exec": "ts-node --project tsconfig.server.json", + "ext": "ts" +} diff --git a/book/11-begin/app/package.json b/book/11-begin/app/package.json new file mode 100644 index 00000000..1a6230a0 --- /dev/null +++ b/book/11-begin/app/package.json @@ -0,0 +1,59 @@ +{ + "name": "11-begin-app", + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": "12.16.1", + "yarn": "1.22.5" + }, + "scripts": { + "dev": "nodemon server/server.ts", + "lint": "eslint . --ext .ts,.tsx", + "postinstall": "rm -rf production-server/", + "build": "next build && tsc --project tsconfig.server.json && cp server/robots.txt production-server", + "start": "node production-server/server.js" + }, + "dependencies": { + "@material-ui/core": "^4.11.4", + "@material-ui/icons": "^4.11.2", + "@material-ui/lab": "^4.0.0-alpha.56", + "@material-ui/styles": "^4.11.4", + "@stripe/stripe-js": "^1.9.0", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "he": "^1.2.0", + "isomorphic-unfetch": "^3.1.0", + "keycode": "^2.2.0", + "lru-cache": "^6.0.0", + "marked": "^1.2.7", + "mobx": "^6.3.1", + "mobx-react": "6.3.1", + "moment": "^2.29.1", + "next": "^9.1.2", + "nprogress": "0.2.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-mentions": "3.0.2", + "sitemap": "^6.3.5", + "socket.io-client": "^3.0.4", + "typescript": "^4.1.3" + }, + "devDependencies": { + "@types/express": "^4.11.1", + "@types/he": "^0.5.29", + "@types/marked": "^0.3.0", + "@types/node": "^12.12.2", + "@types/react": "^16.8.24", + "@types/react-dom": "^16.0.5", + "@types/socket.io-client": "^1.4.33", + "@typescript-eslint/eslint-plugin": "^4.2.0", + "@typescript-eslint/parser": "^4.2.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^7.1.0", + "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-react": "^7.21.5", + "nodemon": "^2.0.7", + "prettier": "^2.2.1", + "ts-node": "^9.1.1" + } +} diff --git a/book/11-begin/app/pages/_app.tsx b/book/11-begin/app/pages/_app.tsx new file mode 100644 index 00000000..57f6356f --- /dev/null +++ b/book/11-begin/app/pages/_app.tsx @@ -0,0 +1,153 @@ +import CssBaseline from '@material-ui/core/CssBaseline'; +import { ThemeProvider } from '@material-ui/styles'; +import { Provider } from 'mobx-react'; +import App from 'next/app'; +import Head from 'next/head'; +import React from 'react'; + +import { themeDark, themeLight } from '../lib/theme'; +import { getUserApiMethod } from '../lib/api/public'; +import { getInitialDataApiMethod } from '../lib/api/team-member'; +import { isMobile } from '../lib/isMobile'; +import { getStore, initializeStore, Store } from '../lib/store'; + +class MyApp extends App { + public static async getInitialProps({ Component, ctx }) { + let firstGridItem = true; + let teamRequired = false; + + if ( + ctx.pathname.includes('/login') || + ctx.pathname.includes('/create-team') || + ctx.pathname.includes('/invitation') + ) { + firstGridItem = false; + } + + if ( + ctx.pathname.includes('/your-settings') || // because of MenuWithLinks inside `Layout` HOC + ctx.pathname.includes('/team-settings') || + ctx.pathname.includes('/discussion') || + ctx.pathname.includes('/billing') + ) { + teamRequired = true; + } + + const { teamSlug, redirectMessage, discussionSlug } = ctx.query; + + const pageProps = { + isMobile: isMobile({ req: ctx.req }), + firstGridItem, + teamRequired, + teamSlug, + redirectMessage, + discussionSlug, + }; + + if (Component.getInitialProps) { + Object.assign(pageProps, await Component.getInitialProps(ctx)); + } + + const appProps = { pageProps }; + + const store = getStore(); + if (store) { + return appProps; + } + + let userObj = null; + try { + const { user } = await getUserApiMethod(ctx.req); + userObj = user; + } catch (error) { + console.log(error); + } + + let initialData; + + if (userObj) { + try { + initialData = await getInitialDataApiMethod({ + request: ctx.req, + data: { teamSlug, discussionSlug }, + }); + } catch (error) { + console.error(error); + } + } + + // console.log(initialData); + + // console.log(teamSlug); + + let selectedTeamSlug = ''; + + if (teamRequired) { + selectedTeamSlug = teamSlug; + } else if (userObj) { + selectedTeamSlug = userObj.defaulTeamSlug; + } + + let team; + if (initialData && initialData.teams) { + team = initialData.teams.find((t) => { + return t.slug === selectedTeamSlug; + }); + } + + return { + ...appProps, + initialState: { user: userObj, currentUrl: ctx.asPath, team, teamSlug, ...initialData }, + }; + } + + public componentDidMount() { + // Remove the server-side injected CSS. + const jssStyles = document.querySelector('#jss-server-side'); + if (jssStyles && jssStyles.parentNode) { + jssStyles.parentNode.removeChild(jssStyles); + } + } + + private store: Store; + + constructor(props) { + super(props); + + console.log('MyApp.constructor'); + + this.store = initializeStore(props.initialState); + } + + public render() { + const { Component, pageProps } = this.props; + const store = this.store; + + const isThemeDark = store.currentUser ? store.currentUser.darkTheme : true; + + const isServer = typeof window === 'undefined'; + + return ( + + + + + + + + + + + + ); + } +} + +export default MyApp; diff --git a/book/11-begin/app/pages/_document.tsx b/book/11-begin/app/pages/_document.tsx new file mode 100644 index 00000000..da770a73 --- /dev/null +++ b/book/11-begin/app/pages/_document.tsx @@ -0,0 +1,137 @@ +import { ServerStyleSheets } from '@material-ui/styles'; +import Document, { Head, Html, Main, NextScript } from 'next/document'; +import React from 'react'; + +class MyDocument extends Document { + public static getInitialProps = async (ctx) => { + // Render app and page and get the context of the page with collected side effects. + const sheets = new ServerStyleSheets(); + const originalRenderPage = ctx.renderPage; + + ctx.renderPage = () => + originalRenderPage({ + enhanceApp: (App) => (props) => sheets.collect(), + }); + + const initialProps = await Document.getInitialProps(ctx); + + return { + ...initialProps, + // Styles fragment is rendered after the app and page rendering finish. + styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()], + }; + }; + + public render() { + // console.log('rendered on the server'); + + const isThemeDark = this.props.__NEXT_DATA__.props.initialState.user + ? this.props.__NEXT_DATA__.props.initialState.user.darkTheme + : true; + + return ( + + + + + + + + + + + +