Преглед изворни кода

Merge branch 'feat/growi-bot' into feat/GW-5798-enable-display-mulutiple-ws-names-and-growi-app-names

Shun Miyazawa пре 4 година
родитељ
комит
5b1e0760e0
26 измењених фајлова са 549 додато и 440 уклоњено
  1. 0 1
      packages/slack/package.json
  2. 15 4
      packages/slack/src/middlewares/verify-slack-request.ts
  3. 11 0
      packages/slack/src/utils/logger/index.ts
  4. 14 19
      packages/slack/src/utils/slash-command-parser.test.ts
  5. 7 2
      packages/slack/src/utils/webclient-factory.ts
  6. 8 0
      packages/slackbot-proxy/src/Server.ts
  7. 25 3
      packages/slackbot-proxy/src/controllers/slack.ts
  8. 6 9
      packages/slackbot-proxy/src/services/RegisterService.ts
  9. 12 0
      resource/locales/en_US/admin/admin.json
  10. 12 0
      resource/locales/ja_JP/admin/admin.json
  11. 12 0
      resource/locales/zh_CN/admin/admin.json
  12. 14 4
      src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx
  13. 163 5
      src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettingsAccordion.jsx
  14. 18 20
      src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxyIntegrationCard.jsx
  15. 29 3
      src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx
  16. 17 17
      src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx
  17. 65 0
      src/client/js/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx
  18. 81 6
      src/client/js/components/Admin/SlackIntegration/OfficialbotSettingsAccordion.jsx
  19. 5 28
      src/client/js/components/Admin/SlackIntegration/SlackIntegration.jsx
  20. 0 1
      src/client/styles/scss/_admin.scss
  21. 1 0
      src/server/models/index.js
  22. 10 0
      src/server/models/slack-app-integration.js
  23. 1 1
      src/server/routes/apiv3/index.js
  24. 0 0
      src/server/routes/apiv3/slack-integration-settings.js
  25. 17 4
      src/server/service/slackbot.js
  26. 6 313
      yarn.lock

+ 0 - 1
packages/slack/package.json

@@ -21,7 +21,6 @@
     "universal-bunyan": "^0.9.2"
     "universal-bunyan": "^0.9.2"
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@slack/bolt": "^3.3.0",
     "@types/express": "^4.17.11",
     "@types/express": "^4.17.11",
     "@types/jest": "^26.0.22",
     "@types/jest": "^26.0.22",
     "@typescript-eslint/eslint-plugin": "^4.18.0",
     "@typescript-eslint/eslint-plugin": "^4.18.0",

+ 15 - 4
packages/slack/src/middlewares/verify-slack-request.ts

@@ -2,8 +2,11 @@ import { createHmac, timingSafeEqual } from 'crypto';
 import { stringify } from 'qs';
 import { stringify } from 'qs';
 import { Response, NextFunction } from 'express';
 import { Response, NextFunction } from 'express';
 
 
+import loggerFactory from '../utils/logger';
 import { RequestFromSlack } from '../interfaces/request-from-slack';
 import { RequestFromSlack } from '../interfaces/request-from-slack';
 
 
+const logger = loggerFactory('@growi/slack:middlewares:verify-slack-request');
+
 /**
 /**
  * Verify if the request came from slack
  * Verify if the request came from slack
  * See: https://api.slack.com/authentication/verifying-requests-from-slack
  * See: https://api.slack.com/authentication/verifying-requests-from-slack
@@ -12,7 +15,9 @@ export const verifySlackRequest = (req: RequestFromSlack, res: Response, next: N
   const signingSecret = req.slackSigningSecret;
   const signingSecret = req.slackSigningSecret;
 
 
   if (signingSecret == null) {
   if (signingSecret == null) {
-    return res.status(400).send({ message: 'No signing secret.' });
+    const message = 'No signing secret.';
+    logger.warn(message, { body: req.body });
+    return res.status(400).send({ message });
   }
   }
 
 
   // take out slackSignature and timestamp from header
   // take out slackSignature and timestamp from header
@@ -20,13 +25,17 @@ export const verifySlackRequest = (req: RequestFromSlack, res: Response, next: N
   const timestamp = req.headers['x-slack-request-timestamp'];
   const timestamp = req.headers['x-slack-request-timestamp'];
 
 
   if (slackSignature == null || timestamp == null) {
   if (slackSignature == null || timestamp == null) {
-    return res.status(403).send({ message: 'Forbidden. Enter from Slack workspace' });
+    const message = 'Forbidden. Enter from Slack workspace';
+    logger.warn(message, { body: req.body });
+    return res.status(403).send({ message });
   }
   }
 
 
   // protect against replay attacks
   // protect against replay attacks
   const time = Math.floor(new Date().getTime() / 1000);
   const time = Math.floor(new Date().getTime() / 1000);
   if (Math.abs(time - timestamp) > 300) {
   if (Math.abs(time - timestamp) > 300) {
-    return res.send('Verification failed.');
+    const message = 'Verification failed.';
+    logger.warn(message, { body: req.body });
+    return res.status(403).send({ message });
   }
   }
 
 
   // generate growi signature
   // generate growi signature
@@ -41,5 +50,7 @@ export const verifySlackRequest = (req: RequestFromSlack, res: Response, next: N
     return next();
     return next();
   }
   }
 
 
-  return res.send('Verification failed.');
+  const message = 'Verification failed.';
+  logger.warn(message, { body: req.body });
+  return res.status(403).send({ message });
 };
 };

+ 11 - 0
packages/slack/src/utils/logger/index.ts

@@ -0,0 +1,11 @@
+import Logger from 'bunyan';
+import { createLogger } from 'universal-bunyan';
+
+const loggerFactory = function(name: string): Logger {
+  return createLogger({
+    name,
+    config: { default: 'info' },
+  });
+};
+
+export default loggerFactory;

+ 14 - 19
packages/slack/src/utils/slash-command-parser.test.ts

@@ -1,19 +1,14 @@
-import { SlashCommand } from '@slack/bolt';
 import { InvalidGrowiCommandError } from '../models/errors';
 import { InvalidGrowiCommandError } from '../models/errors';
 
 
 import { parseSlashCommand } from './slash-command-parser';
 import { parseSlashCommand } from './slash-command-parser';
 
 
-const SlashCommandMock = jest.fn<SlashCommand, [string]>().mockImplementation((text) => {
-  return { text } as SlashCommand;
-});
-
 describe('parseSlashCommand', () => {
 describe('parseSlashCommand', () => {
 
 
   describe('without growiCommandType', () => {
   describe('without growiCommandType', () => {
     test('throws InvalidGrowiCommandError', () => {
     test('throws InvalidGrowiCommandError', () => {
       // setup
       // setup
-      const slashCommandText = '';
-      const slashCommand = new SlashCommandMock(slashCommandText);
+      const text = '';
+      const slashCommand = { text };
 
 
       // when/then
       // when/then
       expect(() => {
       expect(() => {
@@ -24,56 +19,56 @@ describe('parseSlashCommand', () => {
 
 
   test('returns a GrowiCommand instance with empty growiCommandArgs', () => {
   test('returns a GrowiCommand instance with empty growiCommandArgs', () => {
     // setup
     // setup
-    const slashCommandText = 'search';
-    const slashCommand = new SlashCommandMock(slashCommandText);
+    const text = 'search';
+    const slashCommand = { text };
 
 
     // when
     // when
     const result = parseSlashCommand(slashCommand);
     const result = parseSlashCommand(slashCommand);
 
 
     // then
     // then
-    expect(result.text).toBe(slashCommandText);
+    expect(result.text).toBe(text);
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandArgs).toStrictEqual([]);
     expect(result.growiCommandArgs).toStrictEqual([]);
   });
   });
 
 
   test('returns a GrowiCommand instance with space growiCommandType', () => {
   test('returns a GrowiCommand instance with space growiCommandType', () => {
     // setup
     // setup
-    const slashCommandText = '   search   ';
-    const slashCommand = new SlashCommandMock(slashCommandText);
+    const text = '   search   ';
+    const slashCommand = { text };
 
 
     // when
     // when
     const result = parseSlashCommand(slashCommand);
     const result = parseSlashCommand(slashCommand);
 
 
     // then
     // then
-    expect(result.text).toBe(slashCommandText);
+    expect(result.text).toBe(text);
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandArgs).toStrictEqual([]);
     expect(result.growiCommandArgs).toStrictEqual([]);
   });
   });
 
 
   test('returns a GrowiCommand instance with space growiCommandArgs', () => {
   test('returns a GrowiCommand instance with space growiCommandArgs', () => {
     // setup
     // setup
-    const slashCommandText = '   search hoge   ';
-    const slashCommand = new SlashCommandMock(slashCommandText);
+    const text = '   search hoge   ';
+    const slashCommand = { text };
 
 
     // when
     // when
     const result = parseSlashCommand(slashCommand);
     const result = parseSlashCommand(slashCommand);
 
 
     // then
     // then
-    expect(result.text).toBe(slashCommandText);
+    expect(result.text).toBe(text);
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandArgs).toStrictEqual(['hoge']);
     expect(result.growiCommandArgs).toStrictEqual(['hoge']);
   });
   });
 
 
   test('returns a GrowiCommand instance', () => {
   test('returns a GrowiCommand instance', () => {
     // setup
     // setup
-    const slashCommandText = 'search keyword1 keyword2';
-    const slashCommand = new SlashCommandMock(slashCommandText);
+    const text = 'search keyword1 keyword2';
+    const slashCommand = { text };
 
 
     // when
     // when
     const result = parseSlashCommand(slashCommand);
     const result = parseSlashCommand(slashCommand);
 
 
     // then
     // then
-    expect(result.text).toBe(slashCommandText);
+    expect(result.text).toBe(text);
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandArgs).toStrictEqual(['keyword1', 'keyword2']);
     expect(result.growiCommandArgs).toStrictEqual(['keyword1', 'keyword2']);
   });
   });

+ 7 - 2
packages/slack/src/utils/webclient-factory.ts

@@ -2,6 +2,11 @@ import { LogLevel, WebClient } from '@slack/web-api';
 
 
 const isProduction = process.env.NODE_ENV === 'production';
 const isProduction = process.env.NODE_ENV === 'production';
 
 
-export const generateWebClient = (botToken: string): WebClient => {
-  return new WebClient(botToken, { logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
+/**
+ * Generate WebClilent instance
+ * @param token Slack Bot Token or Proxy Server URI
+ * @returns
+ */
+export const generateWebClient = (token: string, serverUri?: string): WebClient => {
+  return new WebClient(token, { slackApiUrl: serverUri, logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
 };
 };

+ 8 - 0
packages/slackbot-proxy/src/Server.ts

@@ -75,6 +75,14 @@ export class Server {
   @Inject()
   @Inject()
   injector: InjectorService;
   injector: InjectorService;
 
 
+  $onInit(): Promise<any> | void {
+    const serverUri = process.env.SERVER_URI;
+
+    if (serverUri === undefined) {
+      throw new Error('The environment variable \'SERVER_URI\' must be defined.');
+    }
+  }
+
   $beforeRoutesInit(): void {
   $beforeRoutesInit(): void {
     this.app
     this.app
       .use(cookieParser())
       .use(cookieParser())

+ 25 - 3
packages/slackbot-proxy/src/controllers/slack.ts

@@ -130,7 +130,7 @@ export class SlackCtrl {
 
 
   @Post('/interactions')
   @Post('/interactions')
   @UseBefore(AuthorizeInteractionMiddleware)
   @UseBefore(AuthorizeInteractionMiddleware)
-  async handleInteraction(@Req() req: AuthedReq, @Res() res: Res): Promise<void|string> {
+  async handleInteraction(@Req() req: AuthedReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
     logger.info('receive interaction', req.body);
     logger.info('receive interaction', req.body);
     logger.info('receive interaction', req.authorizeResult);
     logger.info('receive interaction', req.authorizeResult);
 
 
@@ -153,17 +153,39 @@ export class SlackCtrl {
     const { type } = payload;
     const { type } = payload;
 
 
     // register
     // register
+    // response_urls is an array but the element included is only one.
     if (type === 'view_submission' && payload.response_urls[0].action_id === 'submit_growi_url_and_access_tokens') {
     if (type === 'view_submission' && payload.response_urls[0].action_id === 'submit_growi_url_and_access_tokens') {
       await this.registerService.upsertOrderRecord(this.orderRepository, installation, payload);
       await this.registerService.upsertOrderRecord(this.orderRepository, installation, payload);
-      await this.registerService.showProxyUrl(authorizeResult, payload);
+      await this.registerService.notifyServerUriToSlack(authorizeResult, payload);
       return;
       return;
     }
     }
 
 
     /*
     /*
      * forward to GROWI server
      * forward to GROWI server
      */
      */
-    // TODO: forward to GROWI server by GW-5866
+    const relations = await this.relationRepository.find({ installation: installation?.id });
 
 
+    const promises = relations.map((relation: Relation) => {
+      // generate API URL
+      const url = new URL('/_api/v3/slack-integration/proxied/interactions', relation.growiUri);
+      return axios.post(url.toString(), {
+        ...body,
+        tokenPtoG: relation.tokenPtoG,
+      });
+    });
+
+    // pickup PromiseRejectedResult only
+    const results = await Promise.allSettled(promises);
+    const rejectedResults: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected');
+    const botToken = installation?.data.bot?.token;
+
+    try {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      return postEphemeralErrors(rejectedResults, body.channel_id, body.user_id, botToken!);
+    }
+    catch (err) {
+      logger.error(err);
+    }
   }
   }
 
 
   @Post('/events')
   @Post('/events')

+ 6 - 9
packages/slackbot-proxy/src/services/RegisterService.ts

@@ -6,7 +6,6 @@ import { GrowiCommandProcessor } from '~/interfaces/growi-command-processor';
 import { OrderRepository } from '~/repositories/order';
 import { OrderRepository } from '~/repositories/order';
 import { Installation } from '~/entities/installation';
 import { Installation } from '~/entities/installation';
 
 
-
 const isProduction = process.env.NODE_ENV === 'production';
 const isProduction = process.env.NODE_ENV === 'production';
 
 
 @Service()
 @Service()
@@ -37,6 +36,8 @@ export class RegisterService implements GrowiCommandProcessor {
           generateInputSectionBlock('growiDomain', 'GROWI domain', 'contents_input', false, 'https://example.com'),
           generateInputSectionBlock('growiDomain', 'GROWI domain', 'contents_input', false, 'https://example.com'),
           generateInputSectionBlock('growiAccessToken', 'GROWI ACCESS_TOKEN', 'contents_input', false, 'jBMZvpk.....'),
           generateInputSectionBlock('growiAccessToken', 'GROWI ACCESS_TOKEN', 'contents_input', false, 'jBMZvpk.....'),
           generateInputSectionBlock('proxyToken', 'PROXY ACCESS_TOKEN', 'contents_input', false, 'jBMZvpk.....'),
           generateInputSectionBlock('proxyToken', 'PROXY ACCESS_TOKEN', 'contents_input', false, 'jBMZvpk.....'),
+          // added an input block to make response_url enabled and get info (block_id, action_id, channel_id, response_url)
+          // refer to https://api.slack.com/surfaces/modals/using#modal_response_url
           {
           {
             block_id: 'channel_to_post_proxy_url',
             block_id: 'channel_to_post_proxy_url',
             type: 'input',
             type: 'input',
@@ -79,19 +80,15 @@ export class RegisterService implements GrowiCommandProcessor {
     }
     }
   }
   }
 
 
-  async showProxyUrl(
+  async notifyServerUriToSlack(
       // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
       // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
       authorizeResult:AuthorizeResult, payload: any,
       authorizeResult:AuthorizeResult, payload: any,
   ): Promise<void> {
   ): Promise<void> {
 
 
-    // TODO: implement for when proxy URL is undefined by GW-5834
-    let proxyURL;
-    if (process.env.PROXY_URL != null) {
-      proxyURL = process.env.PROXY_URL;
-    }
-
     const { botToken } = authorizeResult;
     const { botToken } = authorizeResult;
 
 
+    const serverUri = process.env.SERVER_URI;
+
     const client = new WebClient(botToken, { logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
     const client = new WebClient(botToken, { logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
 
 
     await client.chat.postEphemeral({
     await client.chat.postEphemeral({
@@ -102,7 +99,7 @@ export class RegisterService implements GrowiCommandProcessor {
       text: 'Proxy URL',
       text: 'Proxy URL',
       blocks: [
       blocks: [
         generateMarkdownSectionBlock('Please enter and update the following Proxy URL to slack bot setting form in your GROWI'),
         generateMarkdownSectionBlock('Please enter and update the following Proxy URL to slack bot setting form in your GROWI'),
-        generateMarkdownSectionBlock(`Proxy URL: ${proxyURL}`),
+        generateMarkdownSectionBlock(`Proxy URL: ${serverUri}`),
       ],
       ],
     });
     });
     return;
     return;

+ 12 - 0
resource/locales/en_US/admin/admin.json

@@ -288,14 +288,26 @@
       "discard": "Discard",
       "discard": "Discard",
       "generate": "Generate"
       "generate": "Generate"
     },
     },
+    "delete": "Delete",
+    "cooperation_procedure": "Cooperation procedure",
     "official_bot_settings": "Official bot Settings",
     "official_bot_settings": "Official bot Settings",
     "custom_bot_without_proxy_settings": "Custom Bot without proxy Settings",
     "custom_bot_without_proxy_settings": "Custom Bot without proxy Settings",
+    "reset": "Reset",
+    "delete_slackbot_settings": "Reset Slack Bot settings",
+    "slackbot_settings_notice": "Reset",
     "accordion": {
     "accordion": {
       "create_bot": "Create Bot",
       "create_bot": "Create Bot",
       "how_to_create_a_bot": "How to create a bot",
       "how_to_create_a_bot": "How to create a bot",
       "how_to_install": "How to install",
       "how_to_install": "How to install",
       "install_bot_to_slack": "Install Bot To Slack",
       "install_bot_to_slack": "Install Bot To Slack",
       "install_now": "Install now",
       "install_now": "Install now",
+      "generate_access_token": "Generate Access Token",
+      "register_for_growi_official_bot_proxy_service": "Register for GROWI Official Bot Proxy Service",
+      "enter_growi_register_on_slack": "Enter `/growi register` on slack",
+      "paste_growi_url": "Enter `http://localhost:3000` for <b>GROWI URL</b>",
+      "enter_access_token_for_growi_and_proxy": "Enter <b>Access Token for GROWI</b> and <b>Access Token for Proxy</b>",
+      "set_proxy_url_on_growi": "Set Proxy URL on GROWI",
+      "enter_proxy_url_and_update": "Enter <b>Proxy URL</b> shown on Slack and click Update",
       "select_install_your_app": "Select \"Install your app\".",
       "select_install_your_app": "Select \"Install your app\".",
       "select_install_to_workspace": "Select \"Install to Workspace\".",
       "select_install_to_workspace": "Select \"Install to Workspace\".",
       "register_official_bot_proxy_service": "Issue Access Token / Register GROWI Official Bot Proxy Service",
       "register_official_bot_proxy_service": "Issue Access Token / Register GROWI Official Bot Proxy Service",

+ 12 - 0
resource/locales/ja_JP/admin/admin.json

@@ -286,13 +286,25 @@
       "discard": "破棄",
       "discard": "破棄",
       "generate": "発行"
       "generate": "発行"
     },
     },
+    "delete": "削除",
+    "cooperation_procedure": "連携手順",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 設定",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 設定",
+    "reset":"リセット",
+    "delete_slackbot_settings": "Slack Bot 設定をリセットする",
+    "slackbot_settings_notice": "リセットします",
     "accordion": {
     "accordion": {
       "create_bot": "Bot を作成する",
       "create_bot": "Bot を作成する",
       "how_to_create_a_bot": "作成方法はこちら",
       "how_to_create_a_bot": "作成方法はこちら",
       "how_to_install": "インストール方法はこちら",
       "how_to_install": "インストール方法はこちら",
       "install_bot_to_slack": "Bot を Slack にインストールする",
       "install_bot_to_slack": "Bot を Slack にインストールする",
       "install_now": "今すぐインストール",
       "install_now": "今すぐインストール",
+      "generate_access_token": "Access Tokenの発行",
+      "register_for_growi_official_bot_proxy_service": "GROWI Official Bot Proxy サービスへの登録",
+      "enter_growi_register_on_slack": "Slack上で `/growi register` と打つ",
+      "paste_growi_url": "<b>GROWI URL</b>には`http://localhost:3000`を貼り付ける",
+      "enter_access_token_for_growi_and_proxy": "上記で発行した<b>Access Token for GROWI</b> と <b>Access Token for Proxy</b>を入れる",
+      "set_proxy_url_on_growi": "ProxyのURLをGROWIに登録する",
+      "enter_proxy_url_and_update": "Slack上に通知された<b>Proxy URL</b>を入力し、更新してください。",
       "select_install_your_app": "Install your app をクリックします。",
       "select_install_your_app": "Install your app をクリックします。",
       "select_install_to_workspace": "Install to Workspace をクリックします。",
       "select_install_to_workspace": "Install to Workspace をクリックします。",
       "register_official_bot_proxy_service": "アクセストークンの発行 / GROWI Official Bot Proxy サービスへの登録",
       "register_official_bot_proxy_service": "アクセストークンの発行 / GROWI Official Bot Proxy サービスへの登録",

+ 12 - 0
resource/locales/zh_CN/admin/admin.json

@@ -296,13 +296,25 @@
       "discard": "丢弃",
       "discard": "丢弃",
       "generate": "生成"
       "generate": "生成"
     },
     },
+    "delete": "取消",
+    "cooperation_procedure": "协作程序",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 设置",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 设置",
+    "reset":"重置",
+    "delete_slackbot_settings": "重置 Slack Bot 设置",
+    "slackbot_settings_notice": "重置",
     "accordion": {
     "accordion": {
       "create_bot": "创建 Bot",
       "create_bot": "创建 Bot",
       "how_to_create_a_bot": "如何创建一个 Bot",
       "how_to_create_a_bot": "如何创建一个 Bot",
       "how_to_install": "点击这里查看安装说明",
       "how_to_install": "点击这里查看安装说明",
       "install_bot_to_slack": "将 Bot 安装到 Slack",
       "install_bot_to_slack": "将 Bot 安装到 Slack",
       "install_now": "现在安装",
       "install_now": "现在安装",
+      "generate_access_token": "生成Access Token",
+      "register_for_growi_official_bot_proxy_service": "注册 GROWI Official Bot Proxy Service",
+      "enter_growi_register_on_slack": "在Slack中,输入`/growi register`。",
+      "paste_growi_url": "将`http://localhost:3000`粘贴到 <b>GROWI URL</b> 网址中",
+      "enter_access_token_for_growi_and_proxy": "插入上面发出的 <b>Access Token for GROWI</b> 和 <b>Access Token for Proxy</b>。",
+      "set_proxy_url_on_growi": "向GROWI注册Proxy的URL",
+      "enter_proxy_url_and_update": "按照Slack上的通知输入并更新你的 <b>Proxy URL</b>。",
       "select_install_your_app": "选择 \"Install your app\"。",
       "select_install_your_app": "选择 \"Install your app\"。",
       "select_install_to_workspace": "选择 \"Install to Workspace\"。",
       "select_install_to_workspace": "选择 \"Install to Workspace\"。",
       "register_official_bot_proxy_service": "发行访问令牌 / 注册 GROWI 官方 Bot 代理服务",
       "register_official_bot_proxy_service": "发行访问令牌 / 注册 GROWI 官方 Bot 代理服务",

+ 14 - 4
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -15,7 +15,7 @@ const CustomBotWithProxySettings = (props) => {
   return (
   return (
     <>
     <>
 
 
-      <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
+      <h2 className="admin-setting-header mb-2">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
 
 
       {/* TODO delete tmp props */}
       {/* TODO delete tmp props */}
       <CustomBotWithProxyIntegrationCard
       <CustomBotWithProxyIntegrationCard
@@ -23,9 +23,19 @@ const CustomBotWithProxySettings = (props) => {
         slackWorkSpaceNames={['wsName1', 'wsName2']}
         slackWorkSpaceNames={['wsName1', 'wsName2']}
         isSlackScopeSet
         isSlackScopeSet
       />
       />
-
-      <div className="my-5 mx-3">
-        <CustomBotWithProxySettingsAccordion />
+      <h2 className="admin-setting-header">{t('admin:slack_integration.cooperation_procedure')}</h2>
+      <div className="mx-3">
+        <div className="d-flex flex-column pull-right">
+          <button
+            className="my-3 btn btn-outline-danger"
+            type="button"
+          ><i className="icon-trash mr-1" />{t('admin:slack_integration.delete')}
+          </button>
+        </div>
+
+        <div className="d-flex flex-column my-5 w-100">
+          <CustomBotWithProxySettingsAccordion />
+        </div>
       </div>
       </div>
     </>
     </>
   );
   );

+ 163 - 5
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettingsAccordion.jsx

@@ -1,9 +1,38 @@
-import React from 'react';
+import React, { useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import Accordion from '../Common/Accordion';
 import Accordion from '../Common/Accordion';
 
 
 const CustomBotWithProxySettingsAccordion = () => {
 const CustomBotWithProxySettingsAccordion = () => {
+  const [testChannel, setTestChannel] = useState('');
+  /* eslint-disable no-unused-vars */
+  // TODO: Add connection Logs
+  const [connectionErrorCode, setConnectionErrorCode] = useState(null);
+  const [connectionErrorMessage, setConnectionErrorMessage] = useState(null);
+  const [connectionSuccessMessage, setConnectionSuccessMessage] = useState(null);
+
   const { t } = useTranslation();
   const { t } = useTranslation();
+
+  // TODO: Handle test button
+  const submitForm = (e) => {
+    e.preventDefault();
+    // eslint-disable-next-line no-console
+    console.log('Form Submitted');
+  };
+
+  const inputTestChannelHandler = (channel) => {
+    setTestChannel(channel);
+  };
+
+  // TODO: Show test logs
+  let value = '';
+  if (connectionErrorMessage != null) {
+    value = [connectionErrorCode, connectionErrorMessage];
+  }
+  if (connectionSuccessMessage != null) {
+    value = connectionSuccessMessage;
+  }
+
   return (
   return (
     <div className="card border-0 rounded-lg shadow overflow-hidden">
     <div className="card border-0 rounded-lg shadow overflow-hidden">
       <Accordion
       <Accordion
@@ -46,14 +75,143 @@ const CustomBotWithProxySettingsAccordion = () => {
         </div>
         </div>
       </Accordion>
       </Accordion>
       <Accordion
       <Accordion
-        title={<><span className="mr-2">③</span>Third Accordion</>}
+        title={(
+          <>
+            <span className="mr-2">③</span>
+            {t('admin:slack_integration.accordion.generate_access_token')}
+            {' / '}
+            {t('admin:slack_integration.accordion.register_for_growi_official_bot_proxy_service')}
+          </>
+        )}
+      >
+        <div className="py-4 px-5">
+          <p className="font-weight-bold">1. {t('admin:slack_integration.accordion.generate_access_token')}</p>
+          <div className="form-group row">
+            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for GROWI</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <div className="form-group row">
+            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for Proxy</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+
+          <div className="row my-3">
+            <div className="mx-auto">
+              <button type="button" className="btn btn-outline-secondary mx-2">{ t('admin:slack_integration.access_token_settings.discard') }</button>
+              <button type="button" className="btn btn-primary mx-2">{ t('admin:slack_integration.access_token_settings.generate') }</button>
+            </div>
+          </div>
+          <p className="font-weight-bold">2. {t('admin:slack_integration.accordion.register_for_growi_official_bot_proxy_service')}</p>
+          <div className="d-flex flex-column align-items-center">
+            <ol className="p-0">
+              <li><p className="ml-2">{t('admin:slack_integration.accordion.enter_growi_register_on_slack')}</p></li>
+              <li>
+                <p
+                  className="ml-2"
+                  // TODO: Add dynamic link
+                  // TODO: Copy to clipboard on click
+                  // TODO: Add logo
+                  // eslint-disable-next-line react/no-danger
+                  dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.paste_growi_url') }}
+                />
+              </li>
+              <li>
+                <p
+                  className="ml-2"
+                  // eslint-disable-next-line react/no-danger
+                  dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.enter_access_token_for_growi_and_proxy') }}
+                />
+              </li>
+            </ol>
+            {/* TODO: Insert photo */}
+            <div className="rounded border w-50 d-flex justify-content-center align-items-center" style={{ height: '15rem' }}>
+              <h1 className="text-muted">参考画像</h1>
+            </div>
+          </div>
+        </div>
+      </Accordion>
+      <Accordion
+        title={<><span className="mr-2">④</span>{t('admin:slack_integration.accordion.set_proxy_url_on_growi')}</>}
       >
       >
-        3
+        <div className="p-4">
+          <p
+            className="text-center"
+            // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.enter_proxy_url_and_update') }}
+          />
+          <div className="form-group row my-4">
+            <label className="text-left text-md-right col-md-3 col-form-label">Proxy URL</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <AdminUpdateButtonRow
+            disabled={false}
+            // TODO: Add Proxy URL submit logic
+            // eslint-disable-next-line no-console
+            onClick={() => console.log('Update')}
+          />
+        </div>
       </Accordion>
       </Accordion>
       <Accordion
       <Accordion
-        title={<><span className="mr-2">④</span>Fourth Accordion</>}
+        title={<><span className="mr-2">⑤</span>{t('admin:slack_integration.accordion.test_connection')}</>}
       >
       >
-        4
+        {/* TODO: Responsive */}
+        <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
+        <div className="d-flex justify-content-center">
+          <form className="form-row justify-content-center" onSubmit={e => submitForm(e)}>
+            <div className="input-group col-8">
+              <div className="input-group-prepend">
+                <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+              </div>
+              <input
+                className="form-control"
+                type="text"
+                value={testChannel}
+                placeholder="Slack Channel"
+                // TODO: Handle test button
+                onChange={e => inputTestChannelHandler(e.target.value)}
+              />
+            </div>
+            <button
+              type="submit"
+              className="btn btn-info mx-3 font-weight-bold"
+              disabled={testChannel.trim() === ''}
+            >Test
+            </button>
+          </form>
+        </div>
+        {connectionErrorMessage != null
+          && <p className="text-danger text-center my-4">{t('admin:slack_integration.accordion.error_check_logs_below')}</p>}
+        {connectionSuccessMessage != null
+          && <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>}
+        <form>
+          <div className="row my-3 justify-content-center">
+            <div className="form-group slack-connection-log col-md-4">
+              <label className="mb-1"><p className="border-info slack-connection-log-title pl-2 m-0">Logs</p></label>
+              <textarea
+                className="form-control card border-info slack-connection-log-body rounded-lg"
+                rows="5"
+                // TODO: Show test logs
+                value={value}
+                readOnly
+              />
+            </div>
+          </div>
+        </form>
       </Accordion>
       </Accordion>
     </div>
     </div>
   );
   );

+ 18 - 20
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxyIntegrationCard.jsx

@@ -23,25 +23,24 @@ const CustomBotWithoutProxyIntegrationCard = (props) => {
       </div>
       </div>
 
 
       <div className="text-center w-25">
       <div className="text-center w-25">
-        {props.isSlackScopeSet && (
-          <div className="mt-5">
-            <p className="text-success small">
-              <i className="fa fa-check mr-1" />
-              {t('admin:slack_integration.integration_sentence.integration_successful')}
-            </p>
-            <hr className="align-self-center admin-border-success border-success"></hr>
-          </div>
-        )}
-        {!props.isSlackScopeSet && (
-          <div className="mt-4">
-            <small
-              className="text-secondary m-0"
-                  // eslint-disable-next-line react/no-danger
-              dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.integration_sentence.integration_is_not_complete') }}
-            />
-            <hr className="align-self-center admin-border-danger border-danger"></hr>
-          </div>
-        )}
+        {/* TODO apply correct condition GW-5895 */}
+        <div className="mt-4">
+          <small
+            className="text-secondary m-0"
+                // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.integration_sentence.integration_is_not_complete') }}
+          />
+          <hr className="align-self-center admin-border-danger border-danger"></hr>
+        </div>
+
+        <div className="mt-5">
+          <p className="text-success small">
+            <i className="fa fa-check mr-1" />
+            {t('admin:slack_integration.integration_sentence.integration_successful')}
+          </p>
+          <hr className="align-self-center admin-border-success border-success"></hr>
+        </div>
+
       </div>
       </div>
 
 
       <div className="card rounded-lg shadow border-0 w-50 admin-bot-card mb-0">
       <div className="card rounded-lg shadow border-0 w-50 admin-bot-card mb-0">
@@ -57,7 +56,6 @@ const CustomBotWithoutProxyIntegrationCard = (props) => {
 CustomBotWithoutProxyIntegrationCard.propTypes = {
 CustomBotWithoutProxyIntegrationCard.propTypes = {
   siteName: PropTypes.string.isRequired,
   siteName: PropTypes.string.isRequired,
   slackWSNameInWithoutProxy: PropTypes.string,
   slackWSNameInWithoutProxy: PropTypes.string,
-  isSlackScopeSet: PropTypes.bool.isRequired,
 };
 };
 
 
 export default CustomBotWithoutProxyIntegrationCard;
 export default CustomBotWithoutProxyIntegrationCard;

+ 29 - 3
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -4,14 +4,31 @@ import PropTypes from 'prop-types';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
 import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
 import CustomBotWithoutProxyIntegrationCard from './CustomBotWithoutProxyIntegrationCard';
 import CustomBotWithoutProxyIntegrationCard from './CustomBotWithoutProxyIntegrationCard';
+import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
 
 
 const CustomBotWithoutProxySettings = (props) => {
 const CustomBotWithoutProxySettings = (props) => {
   const { appContainer } = props;
   const { appContainer } = props;
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const [siteName, setSiteName] = useState('');
   const [siteName, setSiteName] = useState('');
+  const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
+
+  const deleteSlackSettingsHandler = async() => {
+    try {
+      await appContainer.apiv3.put('/slack-integration-settings/custom-bot-without-proxy', {
+        slackSigningSecret: '',
+        slackBotToken: '',
+        currentBotType: '',
+      });
+      toastSuccess('success');
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
 
 
   useEffect(() => {
   useEffect(() => {
     const siteName = appContainer.config.crowi.title;
     const siteName = appContainer.config.crowi.title;
@@ -25,17 +42,28 @@ const CustomBotWithoutProxySettings = (props) => {
       <CustomBotWithoutProxyIntegrationCard
       <CustomBotWithoutProxyIntegrationCard
         siteName={siteName}
         siteName={siteName}
         slackWSNameInWithoutProxy={props.slackWSNameInWithoutProxy}
         slackWSNameInWithoutProxy={props.slackWSNameInWithoutProxy}
-        isSlackScopeSet={props.isSlackScopeSet}
       />
       />
 
 
       <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_settings')}</h2>
       <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_settings')}</h2>
 
 
+      <button
+        className="mx-3 pull-right btn text-danger border-danger"
+        type="button"
+        onClick={() => setIsDeleteConfirmModalShown(true)}
+      >{t('admin:slack_integration.reset')}
+      </button>
+
       <div className="my-5 mx-3">
       <div className="my-5 mx-3">
         <CustomBotWithoutProxySettingsAccordion
         <CustomBotWithoutProxySettingsAccordion
           {...props}
           {...props}
           activeStep={botInstallationStep.CREATE_BOT}
           activeStep={botInstallationStep.CREATE_BOT}
         />
         />
       </div>
       </div>
+      <DeleteSlackBotSettingsModal
+        isOpen={isDeleteConfirmModalShown}
+        onClose={() => setIsDeleteConfirmModalShown(false)}
+        onClickDeleteButton={deleteSlackSettingsHandler}
+      />
     </>
     </>
   );
   );
 };
 };
@@ -50,8 +78,6 @@ CustomBotWithoutProxySettings.propTypes = {
   slackBotToken: PropTypes.string,
   slackBotToken: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
   isRgisterSlackCredentials: PropTypes.bool,
   isRgisterSlackCredentials: PropTypes.bool,
-  isConnectedToSlack: PropTypes.bool,
-  isSlackScopeSet: PropTypes.bool,
   slackWSNameInWithoutProxy: PropTypes.string,
   slackWSNameInWithoutProxy: PropTypes.string,
 };
 };
 
 

+ 17 - 17
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx

@@ -34,7 +34,7 @@ const CustomBotWithoutProxySettingsAccordion = ({
 
 
   const updateSecretTokenHandler = async() => {
   const updateSecretTokenHandler = async() => {
     try {
     try {
-      await appContainer.apiv3.put('/slack-integration-legacy/custom-bot-without-proxy', {
+      await appContainer.apiv3.put('/slack-integration-settings/custom-bot-without-proxy', {
         slackSigningSecret,
         slackSigningSecret,
         slackBotToken,
         slackBotToken,
         currentBotType,
         currentBotType,
@@ -68,7 +68,7 @@ const CustomBotWithoutProxySettingsAccordion = ({
     setConnectionErrorMessage(null);
     setConnectionErrorMessage(null);
     setConnectionSuccessMessage(null);
     setConnectionSuccessMessage(null);
     try {
     try {
-      const res = await appContainer.apiv3.post('/slack-integration-legacy/notification-test-to-slack-work-space', {
+      const res = await appContainer.apiv3.post('/slack-integration-settings/notification-test-to-slack-work-space', {
         channel: testChannel,
         channel: testChannel,
       });
       });
       setConnectionSuccessMessage(res.data.message);
       setConnectionSuccessMessage(res.data.message);
@@ -160,25 +160,25 @@ const CustomBotWithoutProxySettingsAccordion = ({
       >
       >
         <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
         <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
         <div className="d-flex justify-content-center">
         <div className="d-flex justify-content-center">
-          <form className="form-row align-items-center w-25" onSubmit={e => submitForm(e)}>
-            <div className="col-8 input-group-prepend">
-              <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+          <form className="form-row align-items-center" onSubmit={e => submitForm(e)}>
+            <div className="input-group col-8">
+              <div className="input-group-prepend">
+                <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+              </div>
               <input
               <input
-                className="form-control w-100"
+                className="form-control"
                 type="text"
                 type="text"
                 value={testChannel}
                 value={testChannel}
                 placeholder="Slack Channel"
                 placeholder="Slack Channel"
                 onChange={e => inputTestChannelHandler(e.target.value)}
                 onChange={e => inputTestChannelHandler(e.target.value)}
               />
               />
             </div>
             </div>
-            <div className="col-4">
-              <button
-                type="submit"
-                className="btn btn-info mx-3 font-weight-bold"
-                disabled={testChannel.trim() === ''}
-              >Test
-              </button>
-            </div>
+            <button
+              type="submit"
+              className="btn btn-info mx-3 font-weight-bold"
+              disabled={testChannel.trim() === ''}
+            >Test
+            </button>
           </form>
           </form>
         </div>
         </div>
         {connectionErrorMessage != null
         {connectionErrorMessage != null
@@ -187,10 +187,11 @@ const CustomBotWithoutProxySettingsAccordion = ({
           && <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>}
           && <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>}
         <form>
         <form>
           <div className="row my-3 justify-content-center">
           <div className="row my-3 justify-content-center">
-            <div className="form-group slack-connection-log w-25">
-              <label className="mb-1"><p className="border-info slack-connection-log-title pl-2">Logs</p></label>
+            <div className="form-group slack-connection-log col-md-4">
+              <label className="mb-1"><p className="border-info slack-connection-log-title pl-2 m-0">Logs</p></label>
               <textarea
               <textarea
                 className="form-control card border-info slack-connection-log-body rounded-lg"
                 className="form-control card border-info slack-connection-log-body rounded-lg"
+                rows="5"
                 value={value}
                 value={value}
                 readOnly
                 readOnly
               />
               />
@@ -212,7 +213,6 @@ CustomBotWithoutProxySettingsAccordion.propTypes = {
   slackBotTokenEnv: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
   isRegisterSlackCredentials: PropTypes.bool,
   isRegisterSlackCredentials: PropTypes.bool,
   isSendTestMessage: PropTypes.bool,
   isSendTestMessage: PropTypes.bool,
-  isConnectedToSlack: PropTypes.bool,
   fetchSlackIntegrationData: PropTypes.func,
   fetchSlackIntegrationData: PropTypes.func,
   onSetSlackSigningSecret: PropTypes.func,
   onSetSlackSigningSecret: PropTypes.func,
   onSetSlackBotToken: PropTypes.func,
   onSetSlackBotToken: PropTypes.func,

+ 65 - 0
src/client/js/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx

@@ -0,0 +1,65 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+
+import {
+  Button, Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
+
+const DeleteSlackBotSettingsModal = React.memo((props) => {
+  const { t } = props;
+
+  function closeModal() {
+    if (props.onClose == null) {
+      return;
+    }
+
+    props.onClose();
+  }
+
+  function deleteSlackCredentialsHandler() {
+    if (props.onClickDeleteButton == null) {
+      return;
+    }
+    props.onClickDeleteButton();
+
+    closeModal();
+  }
+
+  function closeButtonHandler() {
+    closeModal();
+  }
+
+  return (
+    <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">
+      <ModalHeader tag="h4" toggle={closeButtonHandler} className="bg-danger text-light">
+        <span>
+          <i className="icon-fw icon-fire"></i>
+          {t('admin:slack_integration.delete_slackbot_settings')}
+        </span>
+      </ModalHeader>
+      <ModalBody>
+        {t('admin:slack_integration.slackbot_settings_notice')}
+      </ModalBody>
+      <ModalFooter>
+        <Button onClick={closeButtonHandler}>{t('Cancel')}</Button>
+        <Button color="danger" onClick={deleteSlackCredentialsHandler}>
+          <i className="icon icon-fire"></i>
+          {t('Reset')}
+        </Button>
+      </ModalFooter>
+    </Modal>
+  );
+
+});
+
+DeleteSlackBotSettingsModal.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+
+  isOpen: PropTypes.bool.isRequired,
+  onClose: PropTypes.func,
+  onClickDeleteButton: PropTypes.func,
+};
+
+export default withTranslation()(DeleteSlackBotSettingsModal);

+ 81 - 6
src/client/js/components/Admin/SlackIntegration/OfficialbotSettingsAccordion.jsx

@@ -1,9 +1,16 @@
 import React from 'react';
 import React from 'react';
+import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
+import { withUnstatedContainers } from '../../UnstatedUtils';
 import Accordion from '../Common/Accordion';
 import Accordion from '../Common/Accordion';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+import AppContainer from '../../../services/AppContainer';
 
 
-const OfficialBotSettingsAccordion = () => {
+
+const OfficialBotSettingsAccordion = (props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
+  const { appContainer } = props;
+  const growiUrl = appContainer.config.crowi.url;
 
 
   return (
   return (
     <div className="card border-0 rounded-lg shadow overflow-hidden">
     <div className="card border-0 rounded-lg shadow overflow-hidden">
@@ -30,14 +37,73 @@ const OfficialBotSettingsAccordion = () => {
       <Accordion
       <Accordion
         title={<><span className="mr-2">②</span>{t('admin:slack_integration.accordion.register_official_bot_proxy_service')}</>}
         title={<><span className="mr-2">②</span>{t('admin:slack_integration.accordion.register_official_bot_proxy_service')}</>}
       >
       >
-        {/* TODO: GW-5824 add accordion contents  */}
-        hoge
+        <div className="py-4 px-5">
+          <p className="font-weight-bold">1. Access Tokenの発行</p>
+          <div className="form-group row">
+            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for GROWI</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <div className="form-group row">
+            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for Proxy</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <div className="row my-3">
+            <div className="mx-auto">
+              <button type="button" className="btn btn-outline-secondary mx-2">破棄</button>
+              <button type="button" className="btn btn-primary mx-2">{ t('Update') }</button>
+            </div>
+          </div>
+          <p className="font-weight-bold">2. GROWI Official Bot Proxy サービスへの登録</p>
+          <div className="d-flex flex-column align-items-center">
+            <ol className="p-0">
+              <li><p className="ml-2">Slack上で`/growi register`と打つ</p></li>
+              {/* TODO: Copy to clipboard on click by GW5856 */}
+              <li>
+                <p className="ml-2"><b>GROWI URL</b>には{growiUrl}
+                  <i className="fa fa-clipboard mx-1 text-secondary" aria-hidden="true"></i>
+                  を貼り付ける
+                </p>
+              </li>
+              <li><p className="ml-2">上記で発行した<b>Access Token for GROWI と Access Token for Proxy</b>を入れる</p></li>
+            </ol>
+            {/* TODO: Insert photo by GW5857 */}
+            <div className="rounded border w-50 d-flex justify-content-center align-items-center" style={{ height: '15rem' }}>
+              <h1 className="text-muted">参考画像</h1>
+            </div>
+          </div>
+        </div>
       </Accordion>
       </Accordion>
       <Accordion
       <Accordion
         title={<><span className="mr-2">③</span>{t('admin:slack_integration.accordion.register_proxy_url')}</>}
         title={<><span className="mr-2">③</span>{t('admin:slack_integration.accordion.register_proxy_url')}</>}
       >
       >
-        {/* TODO: GW-5824 add accordion contents  */}
-        hoge
+        <div className="p-4">
+          <p className="text-center">Slack上に通知された<b>Proxy URL</b>を入力し、更新してください。</p>
+          <div className="form-group row my-4">
+            <label className="text-left text-md-right col-md-3 col-form-label">Proxy URL</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <AdminUpdateButtonRow
+            disabled={false}
+            // TODO: Add Proxy URL submit logic
+            // eslint-disable-next-line no-console
+            onClick={() => console.log('Update')}
+          />
+        </div>
       </Accordion>
       </Accordion>
       <Accordion
       <Accordion
         title={<><span className="mr-2">④</span>{t('admin:slack_integration.accordion.test_connection')}</>}
         title={<><span className="mr-2">④</span>{t('admin:slack_integration.accordion.test_connection')}</>}
@@ -78,4 +144,13 @@ const OfficialBotSettingsAccordion = () => {
   );
   );
 };
 };
 
 
-export default OfficialBotSettingsAccordion;
+/**
+ * Wrapper component for using unstated
+ */
+const OfficialBotSettingsAccordionWrapper = withUnstatedContainers(OfficialBotSettingsAccordion, [AppContainer]);
+
+OfficialBotSettingsAccordion.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+};
+
+export default OfficialBotSettingsAccordionWrapper;

+ 5 - 28
src/client/js/components/Admin/SlackIntegration/SlackIntegration.jsx

@@ -22,40 +22,33 @@ const SlackIntegration = (props) => {
   const [slackBotToken, setSlackBotToken] = useState(null);
   const [slackBotToken, setSlackBotToken] = useState(null);
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
   const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
   const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
-  const [isConnectedToSlack, setIsConnectedToSlack] = useState(false);
   const [isRegisterSlackCredentials, setIsRegisterSlackCredentials] = useState(false);
   const [isRegisterSlackCredentials, setIsRegisterSlackCredentials] = useState(false);
   const [isSendTestMessage, setIsSendTestMessage] = useState(false);
   const [isSendTestMessage, setIsSendTestMessage] = useState(false);
   const [slackWSNameInWithoutProxy, setSlackWSNameInWithoutProxy] = useState(null);
   const [slackWSNameInWithoutProxy, setSlackWSNameInWithoutProxy] = useState(null);
-  const [isSlackScopeSet, setIsSlackScopeSet] = useState(false);
 
 
   const fetchSlackWorkSpaceNameInWithoutProxy = useCallback(async() => {
   const fetchSlackWorkSpaceNameInWithoutProxy = useCallback(async() => {
-    if (!isConnectedToSlack) {
-      return setSlackWSNameInWithoutProxy(null);
-    }
+
     try {
     try {
-      const res = await appContainer.apiv3.get('/slack-integration-legacy/custom-bot-without-proxy/slack-workspace-name');
+      const res = await appContainer.apiv3.get('/slack-integration-settings/custom-bot-without-proxy/slack-workspace-name');
       setSlackWSNameInWithoutProxy(res.data.slackWorkSpaceName);
       setSlackWSNameInWithoutProxy(res.data.slackWorkSpaceName);
-      setIsSlackScopeSet(true);
     }
     }
     catch (err) {
     catch (err) {
       if (err[0].message === 'missing_scope') {
       if (err[0].message === 'missing_scope') {
         setSlackWSNameInWithoutProxy(null);
         setSlackWSNameInWithoutProxy(null);
-        setIsSlackScopeSet(false);
         toastError(err, t('admin:slack_integration.set_scope'));
         toastError(err, t('admin:slack_integration.set_scope'));
       }
       }
       else {
       else {
         toastError(err);
         toastError(err);
       }
       }
     }
     }
-  }, [appContainer.apiv3, isConnectedToSlack, t]);
+  }, [appContainer.apiv3, t]);
 
 
   const fetchSlackIntegrationData = useCallback(async() => {
   const fetchSlackIntegrationData = useCallback(async() => {
     try {
     try {
-      const response = await appContainer.apiv3.get('/slack-integration-legacy/');
+      const response = await appContainer.apiv3.get('/slack-integration-settings');
       const { currentBotType, customBotWithoutProxySettings } = response.data.slackBotSettingParams;
       const { currentBotType, customBotWithoutProxySettings } = response.data.slackBotSettingParams;
       const {
       const {
         slackSigningSecret, slackBotToken, slackSigningSecretEnvVars, slackBotTokenEnvVars,
         slackSigningSecret, slackBotToken, slackSigningSecretEnvVars, slackBotTokenEnvVars,
-        isConnectedToSlack,
       } = customBotWithoutProxySettings;
       } = customBotWithoutProxySettings;
 
 
       setCurrentBotType(currentBotType);
       setCurrentBotType(currentBotType);
@@ -63,18 +56,7 @@ const SlackIntegration = (props) => {
       setSlackBotToken(slackBotToken);
       setSlackBotToken(slackBotToken);
       setSlackSigningSecretEnv(slackSigningSecretEnvVars);
       setSlackSigningSecretEnv(slackSigningSecretEnvVars);
       setSlackBotTokenEnv(slackBotTokenEnvVars);
       setSlackBotTokenEnv(slackBotTokenEnvVars);
-      setIsConnectedToSlack(isConnectedToSlack);
-
       fetchSlackWorkSpaceNameInWithoutProxy();
       fetchSlackWorkSpaceNameInWithoutProxy();
-
-      if (isConnectedToSlack) {
-        setIsRegisterSlackCredentials(true);
-      }
-      else {
-        setIsRegisterSlackCredentials(false);
-        setIsSendTestMessage(false);
-      }
-
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -103,7 +85,7 @@ const SlackIntegration = (props) => {
 
 
   const changeCurrentBotSettingsHandler = async() => {
   const changeCurrentBotSettingsHandler = async() => {
     try {
     try {
-      const res = await appContainer.apiv3.put('/slack-integration-legacy/custom-bot-without-proxy', {
+      const res = await appContainer.apiv3.put('/slack-integration-settings/custom-bot-without-proxy', {
         slackSigningSecret: '',
         slackSigningSecret: '',
         slackBotToken: '',
         slackBotToken: '',
         currentBotType: selectedBotType,
         currentBotType: selectedBotType,
@@ -112,13 +94,10 @@ const SlackIntegration = (props) => {
       setSelectedBotType(null);
       setSelectedBotType(null);
       toastSuccess(t('admin:slack_integration.bot_reset_successful'));
       toastSuccess(t('admin:slack_integration.bot_reset_successful'));
       setIsRegisterSlackCredentials(false);
       setIsRegisterSlackCredentials(false);
-      setIsConnectedToSlack(false);
       setSlackSigningSecret(null);
       setSlackSigningSecret(null);
       setSlackBotToken(null);
       setSlackBotToken(null);
-      setIsConnectedToSlack(false);
       setIsSendTestMessage(false);
       setIsSendTestMessage(false);
       setSlackWSNameInWithoutProxy(null);
       setSlackWSNameInWithoutProxy(null);
-      setIsSlackScopeSet(false);
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -136,8 +115,6 @@ const SlackIntegration = (props) => {
         <CustomBotWithoutProxySettings
         <CustomBotWithoutProxySettings
           isSendTestMessage={isSendTestMessage}
           isSendTestMessage={isSendTestMessage}
           isRegisterSlackCredentials={isRegisterSlackCredentials}
           isRegisterSlackCredentials={isRegisterSlackCredentials}
-          isConnectedToSlack={isConnectedToSlack}
-          isSlackScopeSet={isSlackScopeSet}
           slackBotTokenEnv={slackBotTokenEnv}
           slackBotTokenEnv={slackBotTokenEnv}
           slackBotToken={slackBotToken}
           slackBotToken={slackBotToken}
           slackSigningSecretEnv={slackSigningSecretEnv}
           slackSigningSecretEnv={slackSigningSecretEnv}

+ 0 - 1
src/client/styles/scss/_admin.scss

@@ -114,7 +114,6 @@ $slack-work-space-name-card-border: #efc1f6;
       border-left: 2px solid;
       border-left: 2px solid;
     }
     }
     .slack-connection-log-body {
     .slack-connection-log-body {
-      min-height: 6rem;
       border: 2px solid;
       border: 2px solid;
     }
     }
   }
   }

+ 1 - 0
src/server/models/index.js

@@ -18,4 +18,5 @@ module.exports = {
   GlobalNotificationMailSetting: require('./GlobalNotificationSetting/GlobalNotificationMailSetting'),
   GlobalNotificationMailSetting: require('./GlobalNotificationSetting/GlobalNotificationMailSetting'),
   GlobalNotificationSlackSetting: require('./GlobalNotificationSetting/GlobalNotificationSlackSetting'),
   GlobalNotificationSlackSetting: require('./GlobalNotificationSetting/GlobalNotificationSlackSetting'),
   ShareLink: require('./share-link'),
   ShareLink: require('./share-link'),
+  SlackAppIntegration: require('./slack-app-integration'),
 };
 };

+ 10 - 0
src/server/models/slack-app-integration.js

@@ -0,0 +1,10 @@
+module.exports = function(crowi) {
+  const mongoose = require('mongoose');
+
+  const slackAppIntegrationSchema = new mongoose.Schema({
+    tokenGtoP: { type: String, required: true, unique: true },
+    tokenPtoG: { type: String, required: true, unique: true },
+  });
+
+  return mongoose.model('SlackAppIntegration', slackAppIntegrationSchema);
+};

+ 1 - 1
src/server/routes/apiv3/index.js

@@ -47,7 +47,7 @@ module.exports = (crowi) => {
   router.use('/attachment', require('./attachment')(crowi));
   router.use('/attachment', require('./attachment')(crowi));
 
 
   router.use('/slack-integration', require('./slack-integration')(crowi));
   router.use('/slack-integration', require('./slack-integration')(crowi));
-  router.use('/slack-integration-legacy', require('./slack-integration-legacy')(crowi));
+  router.use('/slack-integration-settings', require('./slack-integration-settings')(crowi));
   router.use('/staffs', require('./staffs')(crowi));
   router.use('/staffs', require('./staffs')(crowi));
 
 
   return router;
   return router;

+ 0 - 0
src/server/routes/apiv3/slack-integration-legacy.js → src/server/routes/apiv3/slack-integration-settings.js


+ 17 - 4
src/server/service/slackbot.js

@@ -3,7 +3,7 @@ const mongoose = require('mongoose');
 
 
 const PAGINGLIMIT = 10;
 const PAGINGLIMIT = 10;
 
 
-const { WebClient, LogLevel } = require('@slack/web-api');
+const { generateWebClient } = require('@growi/slack');
 
 
 const S2sMessage = require('../models/vo/s2s-message');
 const S2sMessage = require('../models/vo/s2s-message');
 const S2sMessageHandlable = require('./s2s-messaging/handlable');
 const S2sMessageHandlable = require('./s2s-messaging/handlable');
@@ -28,10 +28,23 @@ class SlackBotService extends S2sMessageHandlable {
 
 
   async initialize() {
   async initialize() {
     this.isConnectedToSlack = false;
     this.isConnectedToSlack = false;
-    const token = this.crowi.configManager.getConfig('crowi', 'slackbot:token');
+    const currentBotType = this.crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
 
 
-    if (token != null) {
-      this.client = new WebClient(token, { logLevel: LogLevel.DEBUG });
+    if (currentBotType != null) {
+      let serverUri;
+      let token;
+
+      // connect to proxy
+      if (currentBotType !== 'customBotWithoutProxy') {
+        // TODO: https://youtrack.weseek.co.jp/issue/GW-5896
+        serverUri = 'http://localhost:8080/slack-api-proxy/';
+      }
+      // connect directly
+      else {
+        token = this.crowi.configManager.getConfig('crowi', 'slackbot:token');
+      }
+
+      this.client = generateWebClient(token, serverUri);
       logger.debug('SlackBot: setup is done');
       logger.debug('SlackBot: setup is done');
       await this.sendAuthTest();
       await this.sendAuthTest();
     }
     }

+ 6 - 313
yarn.lock

@@ -2679,27 +2679,6 @@
   dependencies:
   dependencies:
     type-detect "4.0.8"
     type-detect "4.0.8"
 
 
-"@slack/bolt@^3.3.0":
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/@slack/bolt/-/bolt-3.3.0.tgz#9bfb9252091f845ab20cac20d6801edae794a169"
-  integrity sha512-rG9OHszzbJB8ZQHUl8v/IR22OodOhx+jGUGm0ZEb0kKmISUNuOGY7FLbPav4soafQg+fKfj4sWtDNfXesZrDag==
-  dependencies:
-    "@slack/logger" "^3.0.0"
-    "@slack/oauth" "^2.0.0"
-    "@slack/socket-mode" "^1.0.0"
-    "@slack/types" "^2.0.0"
-    "@slack/web-api" "^6.0.0"
-    "@types/express" "^4.16.1"
-    "@types/node" ">=12"
-    "@types/promise.allsettled" "^1.0.3"
-    "@types/tsscmp" "^1.0.0"
-    axios "^0.21.1"
-    express "^4.16.4"
-    please-upgrade-node "^3.2.0"
-    promise.allsettled "^1.0.2"
-    raw-body "^2.3.3"
-    tsscmp "^1.0.6"
-
 "@slack/events-api@^3.0.0":
 "@slack/events-api@^3.0.0":
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@slack/events-api/-/events-api-3.0.0.tgz#3e7626ceb5700cb1cce2fbfe6e1583c23a3626fd"
   resolved "https://registry.yarnpkg.com/@slack/events-api/-/events-api-3.0.0.tgz#3e7626ceb5700cb1cce2fbfe6e1583c23a3626fd"
@@ -2725,14 +2704,7 @@
   dependencies:
   dependencies:
     "@types/node" ">=8.9.0"
     "@types/node" ">=8.9.0"
 
 
-"@slack/logger@^3.0.0":
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/@slack/logger/-/logger-3.0.0.tgz#b736d4e1c112c22a10ffab0c2d364620aedcb714"
-  integrity sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==
-  dependencies:
-    "@types/node" ">=12.0.0"
-
-"@slack/oauth@^2.0.0", "@slack/oauth@^2.0.1":
+"@slack/oauth@^2.0.1":
   version "2.0.1"
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/@slack/oauth/-/oauth-2.0.1.tgz#56f8f3cd45258465e2c45860f1ca60e307126e30"
   resolved "https://registry.yarnpkg.com/@slack/oauth/-/oauth-2.0.1.tgz#56f8f3cd45258465e2c45860f1ca60e307126e30"
   integrity sha512-Htiwa70u+uZuWNvYvMjCUuALTl7hMb/1v0sQhrXDDY0dh9tWWUxZCvL6dAR6pxqMCXMjhS3j+tq4o157SGVhRg==
   integrity sha512-Htiwa70u+uZuWNvYvMjCUuALTl7hMb/1v0sQhrXDDY0dh9tWWUxZCvL6dAR6pxqMCXMjhS3j+tq4o157SGVhRg==
@@ -2744,32 +2716,11 @@
     jsonwebtoken "^8.5.1"
     jsonwebtoken "^8.5.1"
     lodash.isstring "^4.0.1"
     lodash.isstring "^4.0.1"
 
 
-"@slack/socket-mode@^1.0.0":
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/@slack/socket-mode/-/socket-mode-1.0.2.tgz#c805a627aa6528b888ad236872d082b40ba4247a"
-  integrity sha512-Rk5FyfZrSXra5xi8u1ZhnzzqOMUcgCcIxTHP1CqnRPymrrC0j1QqzKh7uhQLw1L4Ah9PKs3ObiJe3rXQJtAd6g==
-  dependencies:
-    "@slack/logger" "^3.0.0"
-    "@slack/web-api" "^6.0.0"
-    "@types/node" ">=12.0.0"
-    "@types/p-queue" "^2.3.2"
-    "@types/ws" "^7.2.5"
-    eventemitter3 "^3.1.0"
-    finity "^0.5.4"
-    p-cancelable "^1.1.0"
-    p-queue "^2.4.2"
-    ws "^7.3.1"
-
 "@slack/types@^1.7.0":
 "@slack/types@^1.7.0":
   version "1.10.0"
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.10.0.tgz#cbf7d83e1027f4cbfd13d6b429f120c7fb09127a"
   resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.10.0.tgz#cbf7d83e1027f4cbfd13d6b429f120c7fb09127a"
   integrity sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg==
   integrity sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg==
 
 
-"@slack/types@^2.0.0":
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/@slack/types/-/types-2.0.0.tgz#7b938ab576cd1d6c9ff9ad67a96f8058d101af10"
-  integrity sha512-Nu4jWC39mDY5egAX4oElwOypdu8Cx9tmR7bo3ghaHYaC7mkKM1+b+soanW5s2ssu4yOLxMdFExMh6wlR34B6CA==
-
 "@slack/web-api@^5.7.0":
 "@slack/web-api@^5.7.0":
   version "5.15.0"
   version "5.15.0"
   resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-5.15.0.tgz#6bcf1d0a833c0e87e45150c2fd1f9657e3ec0b0b"
   resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-5.15.0.tgz#6bcf1d0a833c0e87e45150c2fd1f9657e3ec0b0b"
@@ -2786,7 +2737,7 @@
     p-queue "^6.6.1"
     p-queue "^6.6.1"
     p-retry "^4.0.0"
     p-retry "^4.0.0"
 
 
-"@slack/web-api@^6.0.0", "@slack/web-api@^6.1.0":
+"@slack/web-api@^6.1.0":
   version "6.1.0"
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.1.0.tgz#27a17f61eb72100d6722ff17f581349c41d19b5f"
   resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.1.0.tgz#27a17f61eb72100d6722ff17f581349c41d19b5f"
   integrity sha512-9MVHw+rDBaFvkvzm8lDNH/nlkvJCDKRIjFGMdpbyZlVLsm4rcht4qyiL71bqdyLATHXJnWknb/sl0FQGLLobIA==
   integrity sha512-9MVHw+rDBaFvkvzm8lDNH/nlkvJCDKRIjFGMdpbyZlVLsm4rcht4qyiL71bqdyLATHXJnWknb/sl0FQGLLobIA==
@@ -2991,7 +2942,7 @@
     "@types/qs" "*"
     "@types/qs" "*"
     "@types/range-parser" "*"
     "@types/range-parser" "*"
 
 
-"@types/express@*", "@types/express@^4.16.1", "@types/express@^4.17.0", "@types/express@^4.17.11":
+"@types/express@*", "@types/express@^4.17.0", "@types/express@^4.17.11":
   version "4.17.11"
   version "4.17.11"
   resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545"
   resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545"
   integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==
   integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==
@@ -3135,11 +3086,6 @@
   resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
   resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
   integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
   integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
 
 
-"@types/p-queue@^2.3.2":
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/@types/p-queue/-/p-queue-2.3.2.tgz#16bc5fece69ef85efaf2bce8b13f3ebe39c5a1c8"
-  integrity sha512-eKAv5Ql6k78dh3ULCsSBxX6bFNuGjTmof5Q/T6PiECDq0Yf8IIn46jCyp3RJvCi8owaEmm3DZH1PEImjBMd/vQ==
-
 "@types/parse-json@^4.0.0":
 "@types/parse-json@^4.0.0":
   version "4.0.0"
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@@ -3151,11 +3097,6 @@
   dependencies:
   dependencies:
     "@types/express" "*"
     "@types/express" "*"
 
 
-"@types/promise.allsettled@^1.0.3":
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/@types/promise.allsettled/-/promise.allsettled-1.0.3.tgz#6f3166618226a570b98c8250fc78687a912e56d5"
-  integrity sha512-b/IFHHTkYkTqu41IH9UtpICwqrpKj2oNlb4KHPzFQDMiz+h1BgAeATeO0/XTph4+UkH9W2U0E4B4j64KWOovag==
-
 "@types/prop-types@*":
 "@types/prop-types@*":
   version "15.7.3"
   version "15.7.3"
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
@@ -3230,11 +3171,6 @@
   resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
   resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
   integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
   integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
 
 
-"@types/tsscmp@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@types/tsscmp/-/tsscmp-1.0.0.tgz#761c885a530f9673ae6fda0cae38253ffd46cba6"
-  integrity sha512-rj18XR6c4Ohds86Lq8MI1NMRrXes4eLo4H06e5bJyKucE1rXGsfBBbFGD2oDC+DSufQCpnU3TTW7QAiwLx+7Yw==
-
 "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
 "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
   version "2.0.3"
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
   resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
@@ -3256,13 +3192,6 @@
     "@types/unist" "*"
     "@types/unist" "*"
     "@types/vfile-message" "*"
     "@types/vfile-message" "*"
 
 
-"@types/ws@^7.2.5":
-  version "7.4.1"
-  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.1.tgz#49eacb15a0534663d53a36fbf5b4d98f5ae9a73a"
-  integrity sha512-ISCK1iFnR+jYv7+jLNX0wDqesZ/5RAeY3wUx6QaphmocphU61h+b+PHjS18TF4WIPTu/MMzxIq2PHr32o2TS5Q==
-  dependencies:
-    "@types/node" "*"
-
 "@types/yargs-parser@*":
 "@types/yargs-parser@*":
   version "15.0.0"
   version "15.0.0"
   resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
   resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@@ -4015,17 +3944,6 @@ array.prototype.flatmap@^1.2.2:
     es-abstract "^1.15.0"
     es-abstract "^1.15.0"
     function-bind "^1.1.1"
     function-bind "^1.1.1"
 
 
-array.prototype.map@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.3.tgz#1609623618d3d84134a37d4a220030c2bd18420b"
-  integrity sha512-nNcb30v0wfDyIe26Yif3PcV1JXQp4zEeEfupG7L4SRjnD6HLbO5b2a7eVSba53bOx4YCHYMBHt+Fp4vYstneRA==
-  dependencies:
-    call-bind "^1.0.0"
-    define-properties "^1.1.3"
-    es-abstract "^1.18.0-next.1"
-    es-array-method-boxes-properly "^1.0.0"
-    is-string "^1.0.5"
-
 arraybuffer.slice@~0.0.7:
 arraybuffer.slice@~0.0.7:
   version "0.0.7"
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
   resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
@@ -7432,28 +7350,6 @@ es-abstract@^1.15.0:
     string.prototype.trimleft "^2.1.0"
     string.prototype.trimleft "^2.1.0"
     string.prototype.trimright "^2.1.0"
     string.prototype.trimright "^2.1.0"
 
 
-es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2:
-  version "1.18.0"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4"
-  integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==
-  dependencies:
-    call-bind "^1.0.2"
-    es-to-primitive "^1.2.1"
-    function-bind "^1.1.1"
-    get-intrinsic "^1.1.1"
-    has "^1.0.3"
-    has-symbols "^1.0.2"
-    is-callable "^1.2.3"
-    is-negative-zero "^2.0.1"
-    is-regex "^1.1.2"
-    is-string "^1.0.5"
-    object-inspect "^1.9.0"
-    object-keys "^1.1.1"
-    object.assign "^4.1.2"
-    string.prototype.trimend "^1.0.4"
-    string.prototype.trimstart "^1.0.4"
-    unbox-primitive "^1.0.0"
-
 es-abstract@^1.4.3:
 es-abstract@^1.4.3:
   version "1.11.0"
   version "1.11.0"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681"
@@ -7484,25 +7380,6 @@ es-abstract@^1.7.0:
     is-callable "^1.1.3"
     is-callable "^1.1.3"
     is-regex "^1.0.4"
     is-regex "^1.0.4"
 
 
-es-array-method-boxes-properly@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e"
-  integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==
-
-es-get-iterator@^1.0.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7"
-  integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==
-  dependencies:
-    call-bind "^1.0.2"
-    get-intrinsic "^1.1.0"
-    has-symbols "^1.0.1"
-    is-arguments "^1.1.0"
-    is-map "^2.0.2"
-    is-set "^2.0.2"
-    is-string "^1.0.5"
-    isarray "^2.0.5"
-
 es-to-primitive@^1.1.1:
 es-to-primitive@^1.1.1:
   version "1.1.1"
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
   resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
@@ -7519,15 +7396,6 @@ es-to-primitive@^1.2.0:
     is-date-object "^1.0.1"
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
     is-symbol "^1.0.2"
 
 
-es-to-primitive@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
-  integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
-  dependencies:
-    is-callable "^1.1.4"
-    is-date-object "^1.0.1"
-    is-symbol "^1.0.2"
-
 es6-object-assign@^1.1.0:
 es6-object-assign@^1.1.0:
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
   resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
@@ -8028,7 +7896,7 @@ express-webpack-assets@^0.1.0:
   version "0.1.0"
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/express-webpack-assets/-/express-webpack-assets-0.1.0.tgz#000fb3413eb0d512cbd6cd3f6a10b5e70dbe0079"
   resolved "https://registry.yarnpkg.com/express-webpack-assets/-/express-webpack-assets-0.1.0.tgz#000fb3413eb0d512cbd6cd3f6a10b5e70dbe0079"
 
 
-express@^4.0.0, express@^4.16.4, express@^4.17.1:
+express@^4.0.0, express@^4.17.1:
   version "4.17.1"
   version "4.17.1"
   resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
   resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
   integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
   integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
@@ -8502,11 +8370,6 @@ findup-sync@^4.0.0:
     micromatch "^4.0.2"
     micromatch "^4.0.2"
     resolve-dir "^1.0.1"
     resolve-dir "^1.0.1"
 
 
-finity@^0.5.4:
-  version "0.5.4"
-  resolved "https://registry.yarnpkg.com/finity/-/finity-0.5.4.tgz#f2a8a9198e8286467328ec32c8bfcc19a2229c11"
-  integrity sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA==
-
 flat-cache@^2.0.1:
 flat-cache@^2.0.1:
   version "2.0.1"
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
   resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
@@ -8870,15 +8733,6 @@ get-intrinsic@^1.0.2:
     has "^1.0.3"
     has "^1.0.3"
     has-symbols "^1.0.1"
     has-symbols "^1.0.1"
 
 
-get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
-  integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
-  dependencies:
-    function-bind "^1.1.1"
-    has "^1.0.3"
-    has-symbols "^1.0.1"
-
 get-pkg-repo@^1.0.0:
 get-pkg-repo@^1.0.0:
   version "1.4.0"
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d"
   resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d"
@@ -9365,11 +9219,6 @@ has-ansi@^2.0.0:
   dependencies:
   dependencies:
     ansi-regex "^2.0.0"
     ansi-regex "^2.0.0"
 
 
-has-bigints@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
-  integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
-
 has-binary2@~1.0.2:
 has-binary2@~1.0.2:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.2.tgz#e83dba49f0b9be4d026d27365350d9f03f54be98"
   resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.2.tgz#e83dba49f0b9be4d026d27365350d9f03f54be98"
@@ -9403,7 +9252,7 @@ has-symbols@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
 
 
-has-symbols@^1.0.1, has-symbols@^1.0.2:
+has-symbols@^1.0.1:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
   integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
   integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
@@ -10155,7 +10004,7 @@ is-alphanumerical@^1.0.0:
     is-alphabetical "^1.0.0"
     is-alphabetical "^1.0.0"
     is-decimal "^1.0.0"
     is-decimal "^1.0.0"
 
 
-is-arguments@^1.0.4, is-arguments@^1.1.0:
+is-arguments@^1.0.4:
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
   integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==
   integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==
@@ -10171,11 +10020,6 @@ is-arrayish@^0.3.1:
   version "0.3.2"
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
 
 
-is-bigint@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2"
-  integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==
-
 is-binary-path@^1.0.0:
 is-binary-path@^1.0.0:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
   resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
@@ -10189,13 +10033,6 @@ is-binary-path@~2.1.0:
   dependencies:
   dependencies:
     binary-extensions "^2.0.0"
     binary-extensions "^2.0.0"
 
 
-is-boolean-object@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0"
-  integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==
-  dependencies:
-    call-bind "^1.0.0"
-
 is-buffer@^1.1.5, is-buffer@~1.1.1:
 is-buffer@^1.1.5, is-buffer@~1.1.1:
   version "1.1.6"
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@@ -10213,11 +10050,6 @@ is-callable@^1.1.4:
   version "1.1.4"
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
 
 
-is-callable@^1.2.3:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
-  integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
-
 is-ci@^2.0.0:
 is-ci@^2.0.0:
   version "2.0.0"
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
@@ -10384,27 +10216,12 @@ is-lambda@^1.0.1:
   resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
   resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
   integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=
   integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=
 
 
-is-map@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
-  integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==
-
-is-negative-zero@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
-  integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
-
 is-number-like@^1.0.3:
 is-number-like@^1.0.3:
   version "1.0.8"
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3"
   resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3"
   dependencies:
   dependencies:
     lodash.isfinite "^3.3.2"
     lodash.isfinite "^3.3.2"
 
 
-is-number-object@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
-  integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
-
 is-number@^2.1.0:
 is-number@^2.1.0:
   version "2.1.0"
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@@ -10485,14 +10302,6 @@ is-regex@^1.0.4:
   dependencies:
   dependencies:
     has "^1.0.1"
     has "^1.0.1"
 
 
-is-regex@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251"
-  integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
-  dependencies:
-    call-bind "^1.0.2"
-    has-symbols "^1.0.1"
-
 is-regexp@^2.0.0:
 is-regexp@^2.0.0:
   version "2.1.0"
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d"
   resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d"
@@ -10507,11 +10316,6 @@ is-retry-allowed@^1.1.0:
   resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
   resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
   integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
   integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
 
 
-is-set@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec"
-  integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==
-
 is-ssh@^1.3.0:
 is-ssh@^1.3.0:
   version "1.3.2"
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.2.tgz#a4b82ab63d73976fd8263cceee27f99a88bdae2b"
   resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.2.tgz#a4b82ab63d73976fd8263cceee27f99a88bdae2b"
@@ -10528,11 +10332,6 @@ is-stream@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
   integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
   integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
 
 
-is-string@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
-  integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
-
 is-svg@^3.0.0:
 is-svg@^3.0.0:
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75"
   resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75"
@@ -10549,13 +10348,6 @@ is-symbol@^1.0.2:
   dependencies:
   dependencies:
     has-symbols "^1.0.0"
     has-symbols "^1.0.0"
 
 
-is-symbol@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
-  integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
-  dependencies:
-    has-symbols "^1.0.1"
-
 is-text-path@^1.0.1:
 is-text-path@^1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e"
   resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e"
@@ -10615,11 +10407,6 @@ isarray@2.0.1:
   version "2.0.1"
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
 
 
-isarray@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
-  integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
-
 isexe@^2.0.0:
 isexe@^2.0.0:
   version "2.0.0"
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -10699,19 +10486,6 @@ isurl@^1.0.0-alpha5:
     has-to-string-tag-x "^1.2.0"
     has-to-string-tag-x "^1.2.0"
     is-object "^1.0.1"
     is-object "^1.0.1"
 
 
-iterate-iterator@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6"
-  integrity sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==
-
-iterate-value@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/iterate-value/-/iterate-value-1.0.2.tgz#935115bd37d006a52046535ebc8d07e9c9337f57"
-  integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==
-  dependencies:
-    es-get-iterator "^1.0.2"
-    iterate-iterator "^1.0.1"
-
 jake@^10.6.1:
 jake@^10.6.1:
   version "10.8.2"
   version "10.8.2"
   resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
   resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
@@ -13699,16 +13473,6 @@ object.assign@^4.1.0:
     has-symbols "^1.0.0"
     has-symbols "^1.0.0"
     object-keys "^1.0.11"
     object-keys "^1.0.11"
 
 
-object.assign@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
-  integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
-  dependencies:
-    call-bind "^1.0.0"
-    define-properties "^1.1.3"
-    has-symbols "^1.0.1"
-    object-keys "^1.1.1"
-
 object.entries@^1.0.4, object.entries@^1.1.0:
 object.entries@^1.0.4, object.entries@^1.1.0:
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519"
   resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519"
@@ -13939,11 +13703,6 @@ p-cancelable@^0.4.0:
   resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0"
   resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0"
   integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==
   integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==
 
 
-p-cancelable@^1.1.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-defer@^1.0.0:
 p-defer@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
   resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
@@ -14033,11 +13792,6 @@ p-pipe@^3.1.0:
   resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e"
   resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e"
   integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==
   integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==
 
 
-p-queue@^2.4.2:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34"
-  integrity sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng==
-
 p-queue@^6.6.1, p-queue@^6.6.2:
 p-queue@^6.6.1, p-queue@^6.6.2:
   version "6.6.2"
   version "6.6.2"
   resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
   resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
@@ -14605,13 +14359,6 @@ platform@1.3.5:
   version "1.3.5"
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"
   resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"
 
 
-please-upgrade-node@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
-  integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
-  dependencies:
-    semver-compare "^1.0.0"
-
 pluralize@^8.0.0:
 pluralize@^8.0.0:
   version "8.0.0"
   version "8.0.0"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
@@ -15195,18 +14942,6 @@ promise-retry@^2.0.1:
     err-code "^2.0.2"
     err-code "^2.0.2"
     retry "^0.12.0"
     retry "^0.12.0"
 
 
-promise.allsettled@^1.0.2:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.4.tgz#65e71f2a604082ed69c548b68603294090ee6803"
-  integrity sha512-o73CbvQh/OnPFShxHcHxk0baXR2a1m4ozb85ha0H14VEoi/EJJLa9mnPfEWJx9RjA9MLfhdjZ8I6HhWtBa64Ag==
-  dependencies:
-    array.prototype.map "^1.0.3"
-    call-bind "^1.0.2"
-    define-properties "^1.1.3"
-    es-abstract "^1.18.0-next.2"
-    get-intrinsic "^1.0.2"
-    iterate-value "^1.0.2"
-
 promise@^7.1.1:
 promise@^7.1.1:
   version "7.3.1"
   version "7.3.1"
   resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
   resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
@@ -16871,11 +16606,6 @@ scss-tokenizer@^0.2.3:
     js-base64 "^2.1.8"
     js-base64 "^2.1.8"
     source-map "^0.4.2"
     source-map "^0.4.2"
 
 
-semver-compare@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
-  integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
-
 "semver@2 || 3 || 4 || 5", semver@^5.7.1:
 "semver@2 || 3 || 4 || 5", semver@^5.7.1:
   version "5.7.1"
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -17903,14 +17633,6 @@ string.prototype.padend@^3.0.0:
     es-abstract "^1.4.3"
     es-abstract "^1.4.3"
     function-bind "^1.0.2"
     function-bind "^1.0.2"
 
 
-string.prototype.trimend@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
-  integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.3"
-
 string.prototype.trimleft@^2.1.0:
 string.prototype.trimleft@^2.1.0:
   version "2.1.0"
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634"
   resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634"
@@ -17927,14 +17649,6 @@ string.prototype.trimright@^2.1.0:
     define-properties "^1.1.3"
     define-properties "^1.1.3"
     function-bind "^1.1.1"
     function-bind "^1.1.1"
 
 
-string.prototype.trimstart@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
-  integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.3"
-
 string@^3.0.1:
 string@^3.0.1:
   version "3.3.3"
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/string/-/string-3.3.3.tgz#5ea211cd92d228e184294990a6cc97b366a77cb0"
   resolved "https://registry.yarnpkg.com/string/-/string-3.3.3.tgz#5ea211cd92d228e184294990a6cc97b366a77cb0"
@@ -19094,16 +18808,6 @@ umask@^1.1.0:
   resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
   resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
   integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=
   integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=
 
 
-unbox-primitive@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
-  integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==
-  dependencies:
-    function-bind "^1.1.1"
-    has-bigints "^1.0.1"
-    has-symbols "^1.0.2"
-    which-boxed-primitive "^1.0.2"
-
 unherit@^1.0.4:
 unherit@^1.0.4:
   version "1.1.2"
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"
   resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"
@@ -19789,17 +19493,6 @@ when@^3.7.7:
   version "3.7.8"
   version "3.7.8"
   resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82"
   resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82"
 
 
-which-boxed-primitive@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
-  integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
-  dependencies:
-    is-bigint "^1.0.1"
-    is-boolean-object "^1.1.0"
-    is-number-object "^1.0.4"
-    is-string "^1.0.5"
-    is-symbol "^1.0.3"
-
 which-module@^1.0.0:
 which-module@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"