Просмотр исходного кода

Merge branch 'feat/growi-bot' into feat/create-modal-after-typing-growi-register

zahmis 5 лет назад
Родитель
Сommit
36089c6f86
37 измененных файлов с 1575 добавлено и 211 удалено
  1. 7 0
      .github/ISSUE_TEMPLATE/bug-report.md
  2. 5 0
      .github/ISSUE_TEMPLATE/config.yml
  3. 25 0
      .github/ISSUE_TEMPLATE/user-request.md
  4. 16 18
      .github/workflows/ci-slackbot-proxy.yml
  5. 1 1
      .github/workflows/release.yml
  6. 1 1
      .gitignore
  7. 8 0
      lerna.json
  8. 4 1
      package.json
  9. 0 1
      packages/slack/.gitignore
  10. 3 7
      packages/slack/package.json
  11. 4 0
      packages/slack/src/index.ts
  12. 8 8
      packages/slack/src/utils/slash-command-parser.test.ts
  13. 1 1
      packages/slack/src/utils/slash-command-parser.ts
  14. 7 2
      packages/slack/tsconfig.build.json
  15. 8 41
      packages/slack/tsconfig.json
  16. 0 1
      packages/slackbot-proxy/.gitignore
  17. 1 1
      packages/slackbot-proxy/package.json
  18. 5 0
      packages/slackbot-proxy/src/controllers/slack.ts
  19. 0 1
      packages/slackbot-proxy/src/entities/installation.ts
  20. 33 0
      packages/slackbot-proxy/src/entities/relation.ts
  21. 10 0
      packages/slackbot-proxy/src/repositories/relation.ts
  22. 2 2
      packages/slackbot-proxy/src/services/RecieveService.ts
  23. 11 0
      packages/slackbot-proxy/tsconfig.base.json
  24. 7 2
      packages/slackbot-proxy/tsconfig.build.json
  25. 6 42
      packages/slackbot-proxy/tsconfig.json
  26. 26 1
      resource/locales/en_US/admin/admin.json
  27. 26 1
      resource/locales/ja_JP/admin/admin.json
  28. 26 1
      resource/locales/zh_CN/admin/admin.json
  29. 6 13
      src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx
  30. 125 0
      src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx
  31. 144 26
      src/client/js/components/Admin/SlackIntegration/SlackIntegration.jsx
  32. 20 2
      src/client/styles/scss/_admin.scss
  33. 21 0
      src/client/styles/scss/theme/_apply-colors.scss
  34. 39 3
      src/server/routes/apiv3/slack-bot.js
  35. 50 0
      src/server/routes/apiv3/slack-integration.js
  36. 38 0
      tsconfig.base.json
  37. 881 34
      yarn.lock

+ 7 - 0
.github/ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE/bug-report.md

@@ -1,3 +1,10 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: 'Bug:' 
+labels: bug
+---
+
 Environment
 ------------
 

+ 5 - 0
.github/ISSUE_TEMPLATE/config.yml

@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Question or Suggestions
+    url: https://growi-slackin.weseek.co.jp/
+    about: If you have questions or suggestions, you can join our Slack team and talk about anything, anytime.

+ 25 - 0
.github/ISSUE_TEMPLATE/user-request.md

@@ -0,0 +1,25 @@
+---
+name: User request
+about: Suggest an idea for this project
+title: 'Request:'
+labels: user requests
+---
+
+
+## Informations
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. 
+
+e.g. I'm having trouble getting immediate access to information
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to realization.
+
+e.g. It's good if there is a space where everyone can access information in common.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+- [ ] Custom Page In Sidebar
+  - [ ] Can be edited in `/Sidebar` path
+  - [ ] Can be described with md like a page

+ 16 - 18
.github/workflows/ci-slackbot-proxy.yml

@@ -7,15 +7,11 @@ on:
       - tmp/**
     paths:
       - .github/workflows/ci-slackbot-proxy.yml
-      - packages/slack/*
-      - packages/slackbot-proxy/*
+      - packages/slack/**
+      - packages/slackbot-proxy/**
       - package.json
       - yarn.lock
 
-defaults:
-  run:
-    working-directory: packages/slackbot-proxy
-
 jobs:
 
   test:
@@ -36,7 +32,7 @@ jobs:
       uses: actions/cache@v2
       with:
         path: node_modules
-        key: ${{ runner.OS }}-node_modules-${{ matrix.node-version }}-${{ hashFiles('./yarn.lock') }}
+        key: ${{ runner.OS }}-node_modules-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
     - name: Get yarn cache dir
       if: steps.cache-dependencies.outputs.cache-hit != 'true'
       id: cache-yarn
@@ -46,21 +42,21 @@ jobs:
       uses: actions/cache@v2
       with:
         path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('./yarn.lock') }}
+        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
         restore-keys: |
           ${{ runner.os }}-yarn-${{ matrix.node-version }}-
     - name: Install dependencies
       if: steps.cache-dependencies.outputs.cache-hit != 'true'
       run: |
-        yarn --frozen-lockfile
+        npx lerna bootstrap
     - name: Print dependencies
       run: |
         echo -n "node " && node -v
         echo -n "npm " && npm -v
         yarn list --depth=0
-    - name: yarn lint
+    - name: yarn test
       run: |
-        yarn test
+        yarn lerna run test
 
     - name: Slack Notification
       uses: weseek/ghaction-slack-notification@master
@@ -101,7 +97,7 @@ jobs:
       uses: actions/cache@v2
       with:
         path: node_modules
-        key: ${{ runner.OS }}-node_modules_dev-${{ matrix.node-version }}-${{ hashFiles('./yarn.lock') }}
+        key: ${{ runner.OS }}-node_modules_dev-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
     - name: Get yarn cache dir
       if: steps.cache-dependencies.outputs.cache-hit != 'true'
       id: cache-yarn
@@ -111,19 +107,20 @@ jobs:
       uses: actions/cache@v2
       with:
         path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }}
+        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
         restore-keys: |
           ${{ runner.os }}-yarn-
     - name: Install dependencies
       if: steps.cache-dependencies.outputs.cache-hit != 'true'
       run: |
-        yarn --frozen-lockfile
+        npx lerna bootstrap
     - name: Print dependencies
       run: |
         echo -n "node " && node -v
         echo -n "npm " && npm -v
         yarn list --depth=0
     - name: yarn dev:ci
+      working-directory: ./packages/slackbot-proxy
       run: |
         cp config/ci/.env.local.for-ci .env.local
         yarn dev:ci
@@ -191,12 +188,12 @@ jobs:
       uses: actions/cache@v2
       with:
         path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('./yarn.lock') }}
+        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
         restore-keys: |
           ${{ runner.os }}-yarn-
     - name: Install dependencies
       run: |
-        yarn --frozen-lockfile
+        npx lerna bootstrap
     - name: Print dependencies
       run: |
         echo -n "node " && node -v
@@ -204,16 +201,17 @@ jobs:
         yarn list --depth=0
     - name: yarn build
       run: |
-        yarn build
+        yarn lerna run build
     - name: yarn install --production
       run: |
-        yarn install --production
+        yarn lerna exec "yarn install --production"
     - name: Print dependencies
       run: |
         echo -n "node " && node -v
         echo -n "npm " && npm -v
         yarn list --production --depth=0
     - name: yarn start:prod:ci
+      working-directory: ./packages/slackbot-proxy
       run: |
         cp config/ci/.env.local.for-ci .env.local
         yarn start:prod:ci

+ 1 - 1
.github/workflows/release.yml

@@ -125,7 +125,7 @@ jobs:
     - name: Slack Notification
       uses: weseek/ghaction-release-slack-notification@master
       with:
-        channel: '#general'
+        channel: '#release'
         url: ${{ secrets.SLACK_WEBHOOK_URL }}
         created_tag: 'v${{ needs.github-release.outputs.RELEASE_VERSION }}${{ env.SUFFIX }}'
 

+ 1 - 1
.gitignore

@@ -16,7 +16,7 @@ coverage
 /build
 
 # dist
-/dist/
+/packages/**/dist/
 /report/
 /public/static/js
 /public/static/styles

+ 8 - 0
lerna.json

@@ -0,0 +1,8 @@
+{
+  "npmClient": "yarn",
+  "useWorkspaces": true,
+  "packages": [
+    "packages/*"
+  ],
+  "version": "independent"
+}

+ 4 - 1
package.json

@@ -21,7 +21,9 @@
   },
   "private": true,
   "workspaces": {
-    "packages": ["packages/*"],
+    "packages": [
+      "packages/*"
+    ],
     "nohoist": []
   },
   "scripts": {
@@ -219,6 +221,7 @@
     "jquery-slimscroll": "^1.3.8",
     "jquery-ui": "^1.12.1",
     "jquery.cookie": "~1.4.1",
+    "lerna": "^4.0.0",
     "load-css-file": "^1.0.0",
     "lodash-webpack-plugin": "^0.11.5",
     "markdown-it": "^10.0.0",

+ 0 - 1
packages/slack/.gitignore

@@ -1 +0,0 @@
-/lib/

+ 3 - 7
packages/slack/package.json

@@ -2,12 +2,11 @@
   "name": "@growi/slack",
   "version": "0.9.0-RC",
   "license": "MIT",
-  "main": "lib/index.js",
-  "files": ["lib"],
+  "main": "dist/index.js",
+  "typings": "dist/index.d.ts",
   "scripts": {
     "build": "yarn tsc",
     "tsc": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
-    "tsc:w": "tsc -w",
     "test": "yarn test:lint && yarn test:coverage",
     "test:unit": "cross-env NODE_ENV=test jest --passWithNoTests",
     "test:coverage": "yarn test:unit",
@@ -29,10 +28,7 @@
     "eslint-import-resolver-typescript": "^2.4.0",
     "eslint-plugin-jest": "^24.3.2",
     "ts-jest": "^26.5.4",
-    "ts-node": "^9.1.1",
-    "ts-node-dev": "^1.1.6",
-    "tsc-alias": "1.2.6",
-    "tsconfig-paths": "^3.9.0",
+    "tsc-alias": "^1.2.9",
     "typescript": "^4.2.3"
   }
 }

+ 4 - 0
packages/slack/src/index.ts

@@ -6,3 +6,7 @@ export const supportedGrowiCommands: string[] = [
   'search',
   'create',
 ];
+
+export * from './interfaces/growi-command';
+export * from './models/errors';
+export * from './utils/slash-command-parser';

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

@@ -1,13 +1,13 @@
 import { SlashCommand } from '@slack/bolt';
-import { InvalidGrowiCommandError } from '~/models/errors';
+import { InvalidGrowiCommandError } from '../models/errors';
 
-import { parse } from './slash-command-parser';
+import { parseSlashCommand } from './slash-command-parser';
 
 const SlashCommandMock = jest.fn<SlashCommand, [string]>().mockImplementation((text) => {
   return { text } as SlashCommand;
 });
 
-describe('parse SlashCommand', () => {
+describe('parseSlashCommand', () => {
 
   describe('without growiCommandType', () => {
     test('throws InvalidGrowiCommandError', () => {
@@ -17,7 +17,7 @@ describe('parse SlashCommand', () => {
 
       // when/then
       expect(() => {
-        parse(slashCommand);
+        parseSlashCommand(slashCommand);
       }).toThrowError(InvalidGrowiCommandError);
     });
   });
@@ -28,7 +28,7 @@ describe('parse SlashCommand', () => {
     const slashCommand = new SlashCommandMock(slashCommandText);
 
     // when
-    const result = parse(slashCommand);
+    const result = parseSlashCommand(slashCommand);
 
     // then
     expect(result.text).toBe(slashCommandText);
@@ -42,7 +42,7 @@ describe('parse SlashCommand', () => {
     const slashCommand = new SlashCommandMock(slashCommandText);
 
     // when
-    const result = parse(slashCommand);
+    const result = parseSlashCommand(slashCommand);
 
     // then
     expect(result.text).toBe(slashCommandText);
@@ -56,7 +56,7 @@ describe('parse SlashCommand', () => {
     const slashCommand = new SlashCommandMock(slashCommandText);
 
     // when
-    const result = parse(slashCommand);
+    const result = parseSlashCommand(slashCommand);
 
     // then
     expect(result.text).toBe(slashCommandText);
@@ -70,7 +70,7 @@ describe('parse SlashCommand', () => {
     const slashCommand = new SlashCommandMock(slashCommandText);
 
     // when
-    const result = parse(slashCommand);
+    const result = parseSlashCommand(slashCommand);
 
     // then
     expect(result.text).toBe(slashCommandText);

+ 1 - 1
packages/slack/src/utils/slash-command-parser.ts

@@ -1,7 +1,7 @@
 import { GrowiCommand } from '../interfaces/growi-command';
 import { InvalidGrowiCommandError } from '../models/errors';
 
-export const parse = (slashCommand:{[key:string]:string}): GrowiCommand => {
+export const parseSlashCommand = (slashCommand:{[key:string]:string}): GrowiCommand => {
   const trimmedText = slashCommand.text.trim();
   const splitted = trimmedText.split(' ');
 

+ 7 - 2
packages/slack/tsconfig.build.json

@@ -1,12 +1,17 @@
 {
   "extends": "./tsconfig.json",
   "compilerOptions": {
-    "outDir": "lib",
+    "rootDir": "./src",
+    "outDir": "dist",
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
     "sourceMap": true,
     "noEmit": false,
-    "inlineSources": true
+    "inlineSources": true,
+
+    "baseUrl": ".",
+    "paths": {
+    }
   }
 }

+ 8 - 41
packages/slack/tsconfig.json

@@ -1,46 +1,13 @@
 {
+  "extends": "../../tsconfig.base.json",
   "compilerOptions": {
-    "target": "es2019",
-    "module": "commonjs",
-    "lib": ["dom", "dom.iterable", "esnext"],
-    "sourceMap": true,
-    "noEmit": true,
-    "removeComments": false,
-    "importHelpers": true,
-    "isolatedModules": true,
-
-    /* Strict Type-Checking Options */
-    // "strict": true,
-    "strictNullChecks": true,
-    "noImplicitAny": false,
-
-    /* Additional Checks */
-    "noUnusedLocals": false,
-    "noUnusedParameters": false,
-
-    /* Module Resolution Options */
-    "moduleResolution": "node",
-    "baseUrl": "src",
+    "baseUrl": ".",
     "paths": {
-      "~/*": ["./*"],
-      "^/*": ["../*"],
-    },
-    "typeRoots": [
-      "../../node_modules/@types",
-      "./node_modules/@types"
-    ],
-    "allowSyntheticDefaultImports": true,
-    "esModuleInterop": true,
-
-    /* Misc */
-    "preserveConstEnums": true,
-    "forceConsistentCasingInFileNames": true,
-    "resolveJsonModule": true,
-
-    /* Experimental Options */
-    "experimentalDecorators": true,
-    "emitDecoratorMetadata": true
+    }
   },
-  "exclude": ["node_modules", "./public", "dist", "test"],
-  "include": ["./src/**/*.ts"]
+  "exclude": [
+    "node_modules",
+    "dist",
+    "**/*.test.ts"
+  ]
 }

+ 0 - 1
packages/slackbot-proxy/.gitignore

@@ -1 +0,0 @@
-/dist/

+ 1 - 1
packages/slackbot-proxy/package.json

@@ -41,7 +41,7 @@
     "ts-jest": "^26.5.4",
     "ts-node": "^9.1.1",
     "ts-node-dev": "^1.1.6",
-    "tsc-alias": "1.2.6",
+    "tsc-alias": "^1.2.9",
     "tsconfig-paths": "^3.9.0",
     "typescript": "^4.2.3"
   }

+ 5 - 0
packages/slackbot-proxy/src/controllers/slack.ts

@@ -3,9 +3,11 @@ import {
 } from '@tsed/common';
 import { parse } from '@growi/slack/src/utils/slash-command-parser';
 import { Installation } from '~/entities/installation';
+import { Relation } from '~/entities/relation';
 import { Order } from '~/entities/order';
 
 import { InstallationRepository } from '~/repositories/installation';
+import { RelationRepository } from '~/repositories/relation';
 import { OrderRepository } from '~/repositories/order';
 import { InstallerService } from '~/services/InstallerService';
 import { RegisterService } from '~/services/RegisterService';
@@ -20,6 +22,9 @@ export class SlackCtrl {
   @Inject()
   installationRepository: InstallationRepository;
 
+  @Inject()
+  relationRepository: RelationRepository;
+
   @Inject()
   orderRepository: OrderRepository;
 

+ 0 - 1
packages/slackbot-proxy/src/entities/installation.ts

@@ -6,7 +6,6 @@ import {
 } from 'typeorm';
 
 import { Installation as SlackInstallation } from '@slack/oauth';
-import { Order } from './order';
 
 @Entity()
 export class Installation {

+ 33 - 0
packages/slackbot-proxy/src/entities/relation.ts

@@ -0,0 +1,33 @@
+import {
+  Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, ManyToOne, Index,
+} from 'typeorm';
+import { Installation } from './installation';
+
+@Entity()
+@Index(['installation', 'growiUri'], { unique: true })
+export class Relation {
+
+  @PrimaryGeneratedColumn()
+  readonly id: number;
+
+  @CreateDateColumn()
+  readonly createdAt: Date;
+
+  @UpdateDateColumn()
+  readonly updatedAt: Date;
+
+  @ManyToOne(() => Installation)
+  readonly installation: number;
+
+  @Column()
+  @Index({ unique: true })
+  tokenGtoP: string;
+
+  @Column()
+  @Index()
+  tokenPtoG: string;
+
+  @Column()
+  growiUri: string;
+
+}

+ 10 - 0
packages/slackbot-proxy/src/repositories/relation.ts

@@ -0,0 +1,10 @@
+import {
+  Repository, EntityRepository,
+} from 'typeorm';
+
+import { Relation } from '~/entities/relation';
+
+@EntityRepository(Relation)
+export class RelationRepository extends Repository<Relation> {
+
+}

+ 2 - 2
packages/slackbot-proxy/src/services/RecieveService.ts

@@ -1,11 +1,11 @@
 import { Service } from '@tsed/di';
-import { parse } from '@growi/slack/lib/utils/slash-command-parser';
+import { parseSlashCommand } from '@growi/slack';
 
 @Service()
 export class ReceiveService {
 
   receiveContentsFromSlack(body:{[key:string]:string}) : string {
-    const parseBody = parse(body);
+    const parseBody = parseSlashCommand(body);
     if (parseBody.growiCommandType === 'register') {
       console.log('register action occured');
       return 'register action occurd';

+ 11 - 0
packages/slackbot-proxy/tsconfig.base.json

@@ -0,0 +1,11 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+  },
+  "exclude": [
+    "node_modules",
+    "config",
+    "dist",
+    "**/*.test.ts"
+  ]
+}

+ 7 - 2
packages/slackbot-proxy/tsconfig.build.json

@@ -1,12 +1,17 @@
 {
-  "extends": "./tsconfig.json",
+  "extends": "./tsconfig.base.json",
   "compilerOptions": {
+    "rootDir": "./src",
     "outDir": "dist",
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
     "sourceMap": true,
     "noEmit": false,
-    "inlineSources": true
+    "inlineSources": true,
+    "baseUrl": "./src",
+    "paths": {
+      "~/*": ["./*"]
+    }
   }
 }

+ 6 - 42
packages/slackbot-proxy/tsconfig.json

@@ -1,46 +1,10 @@
 {
+  "extends": "./tsconfig.base.json",
   "compilerOptions": {
-    "target": "es2019",
-    "module": "commonjs",
-    "lib": ["dom", "dom.iterable", "esnext"],
-    "sourceMap": true,
-    "noEmit": true,
-    "removeComments": false,
-    "importHelpers": true,
-    "isolatedModules": true,
-
-    /* Strict Type-Checking Options */
-    // "strict": true,
-    "strictNullChecks": true,
-    "noImplicitAny": false,
-
-    /* Additional Checks */
-    "noUnusedLocals": false,
-    "noUnusedParameters": false,
-
-    /* Module Resolution Options */
-    "moduleResolution": "node",
-    "baseUrl": "src",
+    "baseUrl": ".",
     "paths": {
-      "~/*": ["./*"],
-      "^/*": ["../*"],
-    },
-    "typeRoots": [
-      "../../node_modules/@types",
-      "./node_modules/@types"
-    ],
-    "allowSyntheticDefaultImports": true,
-    "esModuleInterop": true,
-
-    /* Misc */
-    "preserveConstEnums": true,
-    "forceConsistentCasingInFileNames": true,
-    "resolveJsonModule": true,
-
-    /* Experimental Options */
-    "experimentalDecorators": true,
-    "emitDecoratorMetadata": true
-  },
-  "exclude": ["node_modules", "./public", "dist", "test"],
-  "include": ["./src/**/*.ts"]
+      "~/*": ["./src/*"],
+      "@growi/*": ["../*/src"]
+    }
+  }
 }

+ 26 - 1
resource/locales/en_US/admin/admin.json

@@ -255,6 +255,27 @@
     "delete": "Delete"
   },
   "slack_integration": {
+    "selecting_bot_types": {
+      "slack_bot": "Slack bot",
+      "detailed_explanation": "Detailed explanation",
+      "selecting_bot_type": "・Select bot type",
+      "official_bot": "Official bot",
+      "custom_bot": "Custom bot",
+      "without_proxy": "without proxy",
+      "with_proxy": "with proxy",
+      "recommended": "Recommended",
+      "for_beginners": "- For beginners -",
+      "for_intermediate": "- For intermediates -",
+      "for_advanced": "- For advanced -",
+      "set_up": "Set up",
+      "multiple_workspaces_integration": "Multiple workspaces integration",
+      "security_control": "Security control",
+      "easy": "Easy",
+      "normal": "Normal",
+      "hard": "Hard",
+      "possible": "Possible",
+      "impossible": "Impossible"
+    },
     "bot_reset_successful": "Bot settings have been reset.",
     "copied_to_clipboard": "Copied to clipboard",
     "modal": {
@@ -271,7 +292,11 @@
     },
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) Settings",
     "without_proxy": {
-      "create_bot": "Create Bot"
+      "create_bot": "Create Bot",
+      "install_bot_to_slack": "Install Bot To Slack",
+      "register_secret_and_token": "Set Signing Secret and Bot Token",
+      "test_connection": "Test Connection",
+      "how_to_create_a_bot": "How to create a bot"
     }
   },
   "user_management": {

+ 26 - 1
resource/locales/ja_JP/admin/admin.json

@@ -253,6 +253,27 @@
     "Directory_hierarchy_tag": "ディレクトリ階層タグ"
   },
   "slack_integration": {
+    "selecting_bot_types": {
+      "slack_bot": "Slack bot",
+      "detailed_explanation": "詳しい説明はこちら",
+      "selecting_bot_type": "・Botタイプを選択する",
+      "official_bot": "Official bot",
+      "custom_bot": "Custom bot",
+      "without_proxy": "without proxy",
+      "with_proxy": "with proxy",
+      "recommended": "おすすめ",
+      "for_beginners": "- 初心者向け -",
+      "for_intermediate": "- 中級者向け -",
+      "for_advanced": "- 上級者向け -",
+      "set_up": "セットアップ",
+      "multiple_workspaces_integration": "複数ワークスペースとの連携",
+      "security_control": "セキュリティコントロール",
+      "easy": "かんたん",
+      "normal": "ふつう",
+      "hard": "むずかしい",
+      "possible": "可能",
+      "impossible": "不可"
+    },
     "bot_reset_successful": "Botの設定を消去しました。",
     "copied_to_clipboard": "クリップボードにコピーされました。",
     "modal": {
@@ -269,7 +290,11 @@
     },
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 設定",
     "without_proxy": {
-      "create_bot": "Bot を作成する"
+      "create_bot": "Bot を作成する",
+      "install_bot_to_slack": "Bot を Slackにインストールする",
+      "register_secret_and_token": "Signing Secret と Bot Token を登録する",
+      "test_connection": "連携状況のテストをする",
+      "how_to_create_a_bot": "作成方法はこちら"
     }
   },
   "user_management": {

+ 26 - 1
resource/locales/zh_CN/admin/admin.json

@@ -263,6 +263,27 @@
 		"delete": "删除"
   },
   "slack_integration": {
+    "selecting_bot_types": {
+      "slack_bot": "Slack bot",
+      "detailed_explanation": "详细说明",
+      "selecting_bot_type": "・选择机器人类型",
+      "official_bot": "Official bot",
+      "custom_bot": "Custom bot",
+      "without_proxy": "without proxy",
+      "with_proxy": "with proxy",
+      "recommended": "受到推崇的",
+      "for_beginners": "- 对于初学者 -",
+      "for_intermediate": "- 对于中级 -",
+      "for_advanced": "- 对于高级 -",
+      "set_up": "设置",
+      "multiple_workspaces_integration": "集成到多个工作区",
+      "security_control": "安全控制",
+      "easy": "简单",
+      "normal": "通常",
+      "hard": "难的",
+      "possible": "可能的",
+      "impossible": "不可能"
+    },
     "bot_reset_successful": "删除了BOT设置。",
     "copied_to_clipboard": "它已复制到剪贴板。",
     "modal": {
@@ -279,7 +300,11 @@
     },
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 设置",
     "without_proxy": {
-      "create_bot": "创建 Bot"
+      "create_bot": "创建 Bot",
+      "install_bot_to_slack": "将Bot安装到Slack",
+      "register_secret_and_token": "设置签名秘密和BOT令牌",
+      "test_connection": "测试连接",
+      "how_to_create_a_bot": "如何创建一个BOT"
     }
   },
 	"user_management": {

+ 6 - 13
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -7,6 +7,7 @@ import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import SlackGrowiBridging from './SlackGrowiBridging';
+import CustomBotWithoutProxySettingsAccordion from './CustomBotWithoutProxySettingsAccordion';
 
 
 const CustomBotWithoutProxySettings = (props) => {
@@ -89,17 +90,6 @@ const CustomBotWithoutProxySettings = (props) => {
         siteName={siteName}
         slackWorkSpaceName={slackWSNameInWithoutProxy}
       />
-      <div className="row my-5">
-        <div className="mx-auto">
-          <button
-            type="button"
-            className="btn btn-primary text-nowrap mx-1"
-            onClick={() => window.open('https://api.slack.com/apps', '_blank')}
-          >
-            {t('admin:slack_integration.without_proxy.create_bot')}
-          </button>
-        </div>
-      </div>
       <table className="table settings-table">
         <colgroup>
           <col className="item-name" />
@@ -155,13 +145,16 @@ const CustomBotWithoutProxySettings = (props) => {
                 <small dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.use_env_var_if_empty', { variable: 'SLACK_BOT_TOKEN' }) }} />
               </p>
             </td>
-
           </tr>
         </tbody>
       </table>
 
-
       <AdminUpdateButtonRow onClick={updateHandler} disabled={false} />
+
+      <div className="my-5 mx-3">
+        <CustomBotWithoutProxySettingsAccordion />
+      </div>
+
     </>
   );
 };

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

@@ -0,0 +1,125 @@
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Collapse } from 'reactstrap';
+
+const CustomBotWithoutSettingsAccordion = () => {
+  const { t } = useTranslation('admin');
+  const [openAccordionIndexes, setOpenAccordionIndexes] = useState(new Set());
+
+  const onToggleAccordionHandler = (i) => {
+    const accordionIndexes = new Set(openAccordionIndexes);
+    if (accordionIndexes.has(i)) {
+      accordionIndexes.delete(i);
+    }
+    else {
+      accordionIndexes.add(i);
+    }
+    setOpenAccordionIndexes(accordionIndexes);
+  };
+
+  return (
+    <div className="card border-0 rounded-lg shadow overflow-hidden">
+
+      <div className="card border-0 rounded-lg mb-0">
+        <div
+          className="card-header font-weight-normal py-3 d-flex justify-content-between"
+          role="button"
+          onClick={() => onToggleAccordionHandler(0)}
+        >
+          <p className="mb-0 text-primary"><span className="mr-2">①</span>{t('slack_integration.without_proxy.create_bot')}</p>
+          {openAccordionIndexes.has(0)
+            ? <i className="fa fa-chevron-up" />
+            : <i className="fa fa-chevron-down" />
+          }
+        </div>
+        <Collapse isOpen={openAccordionIndexes.has(0)}>
+          <div className="card-body">
+
+            <div className="row my-5">
+              <div className="mx-auto">
+                <div>
+                  <button type="button" className="btn btn-primary text-nowrap mx-1" onClick={() => window.open('https://api.slack.com/apps', '_blank')}>
+                    {t('slack_integration.without_proxy.create_bot')}
+                    <i className="fa fa-external-link ml-2" aria-hidden="true" />
+                  </button>
+                </div>
+                {/* TODO: Insert DOCS link */}
+                <a href="#">
+                  <p className="text-center mt-1">
+                    <small>
+                      {t('slack_integration.without_proxy.how_to_create_a_bot')}
+                      <i className="fa fa-external-link ml-2" aria-hidden="true" />
+                    </small>
+                  </p>
+                </a>
+              </div>
+            </div>
+          </div>
+        </Collapse>
+      </div>
+
+      <div className="card border-0 rounded-lg mb-0">
+        <div
+          className="card-header font-weight-normal py-3 d-flex justify-content-between"
+          role="button"
+          onClick={() => onToggleAccordionHandler(1)}
+        >
+          <p className="mb-0 text-primary"><span className="mr-2">②</span>{t('slack_integration.without_proxy.install_bot_to_slack')}</p>
+          {openAccordionIndexes.has(1)
+            ? <i className="fa fa-chevron-up" />
+            : <i className="fa fa-chevron-down" />
+          }
+        </div>
+        <Collapse isOpen={openAccordionIndexes.has(1)}>
+          <div className="card-body">
+            BODY2
+          </div>
+        </Collapse>
+      </div>
+
+      <div className="card border-0 rounded-lg mb-0">
+        <div
+          className="card-header font-weight-normal py-3 d-flex justify-content-between"
+          role="button"
+          onClick={() => onToggleAccordionHandler(2)}
+        >
+          <p className="mb-0 text-primary"><span className="mr-2">③</span>{t('slack_integration.without_proxy.register_secret_and_token')}</p>
+          {openAccordionIndexes.has(2)
+            ? <i className="fa fa-chevron-up" />
+            : <i className="fa fa-chevron-down" />
+          }
+        </div>
+        <Collapse isOpen={openAccordionIndexes.has(2)}>
+          <div className="card-body">
+            BODY 3
+          </div>
+        </Collapse>
+      </div>
+
+      <div className="card border-0 rounded-lg mb-0">
+        <div
+          className="card-header font-weight-normal py-3 d-flex justify-content-between"
+          role="button"
+          onClick={() => onToggleAccordionHandler(3)}
+        >
+          <p className="mb-0 text-primary"><span className="mr-2">④</span>{t('slack_integration.without_proxy.test_connection')}</p>
+          {openAccordionIndexes.has(3)
+            ? <i className="fa fa-chevron-up" />
+            : <i className="fa fa-chevron-down" />
+          }
+        </div>
+        <Collapse isOpen={openAccordionIndexes.has(3)}>
+          <div className="card-body">
+            BODY 4
+          </div>
+        </Collapse>
+      </div>
+
+    </div>
+
+  );
+
+};
+
+
+export default CustomBotWithoutSettingsAccordion;

+ 144 - 26
src/client/js/components/Admin/SlackIntegration/SlackIntegration.jsx

@@ -103,6 +103,16 @@ const SlackIntegration = (props) => {
       break;
   }
 
+  const showBotTypeLevel = (level) => {
+    return <span>{t(`admin:slack_integration.selecting_bot_types.${level}`)}</span>;
+  };
+  const showBotTypeLabel = (label) => {
+    return <span>{t(`admin:slack_integration.selecting_bot_types.${label}`)}</span>;
+  };
+  const showBotTypeDiscription = (desc) => {
+    return <span className={`bot-type-disc-${desc}`}>{t(`admin:slack_integration.selecting_bot_types.${desc}`)}</span>;
+  };
+
   return (
     <>
       <ConfirmBotChangeModal
@@ -117,39 +127,147 @@ const SlackIntegration = (props) => {
         onClickGenerateToken={generateTokenHandler}
       />
 
-      <div className="row my-5">
-        <div className="card-deck mx-auto">
+      <div className="selecting-bot-type my-5">
+        <h2 className="admin-setting-header mb-4">
+          {t('admin:slack_integration.selecting_bot_types.slack_bot')}
+          <span className="ml-2 btn-link">
+            <span className="mr-1">{t('admin:slack_integration.selecting_bot_types.detailed_explanation')}</span>
+            {/* TODO: add an appropriate link by GW-5614 */}
+            <i className="fa fa-external-link" aria-hidden="true"></i>
+          </span>
+
+        </h2>
+
+        {t('admin:slack_integration.selecting_bot_types.selecting_bot_type')}
 
-          <div
-            className={`card admin-bot-card mx-3 py-5 rounded ${currentBotType === 'official-bot' ? 'border-info' : ''}`}
-            onClick={() => handleBotTypeSelect('official-bot')}
-          >
-            <div className="card-body">
-              <h5 className="card-title">Official Bot</h5>
-              <p className="card-text">This is a wider card with supporting text below as a natural lead-in to additional content.</p>
+        <div className="row my-4">
+          <div className="card-deck mx-auto">
+
+            <div
+              className={`card admin-bot-card mx-3 rounded border-radius-sm shadow ${currentBotType === 'official-bot' ? 'border-primary' : ''}`}
+              onClick={() => handleBotTypeSelect('official-bot')}
+              role="button"
+            >
+              <div>
+                <h3 className={`card-header mb-0 py-3 text-center ${currentBotType === 'official-bot' ? 'bg-primary text-light' : ''}`}>
+                  <span className="mr-2">
+                    {t('admin:slack_integration.selecting_bot_types.official_bot')}
+                  </span>
+                  <span className="badge badge-info mr-2">
+                    {t('admin:slack_integration.selecting_bot_types.recommended')}
+                  </span>
+                  {/* TODO: add an appropriate link by GW-5614 */}
+                  <i className={`fa fa-external-link btn-link ${currentBotType === 'official-bot' ? 'bg-primary text-light' : ''}`} aria-hidden="true"></i>
+                </h3>
+              </div>
+              <div className="card-body p-4">
+                <p className="card-text">
+                  <div className="text-center">
+                    {showBotTypeLevel('for_beginners')}
+                  </div>
+                  <div className="my-4">
+                    <div className="d-flex justify-content-between mb-2">
+                      {showBotTypeLabel('set_up')}
+                      {showBotTypeDiscription('easy')}
+                    </div>
+                    <div className="d-flex justify-content-between mb-2">
+                      {showBotTypeLabel('multiple_workspaces_integration')}
+                      {showBotTypeDiscription('possible')}
+                    </div>
+                    <div className="d-flex justify-content-between">
+                      {showBotTypeLabel('security_control')}
+                      {showBotTypeDiscription('impossible')}
+                    </div>
+                  </div>
+                </p>
+              </div>
             </div>
-          </div>
 
-          <div
-            className={`card admin-bot-card mx-3 py-5 rounded ${currentBotType === 'custom-bot-without-proxy' ? 'border-info' : ''}`}
-            onClick={() => handleBotTypeSelect('custom-bot-without-proxy')}
-          >
-            <div className="card-body">
-              <h5 className="card-title">Custom Bot (Without Proxy)</h5>
-              <p className="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. </p>
+            <div
+              className={`card admin-bot-card mx-3 rounded shadow ${currentBotType === 'custom-bot-without-proxy' ? 'border-primary' : ''}`}
+              onClick={() => handleBotTypeSelect('custom-bot-without-proxy')}
+              role="button"
+            >
+              <h3 className={`card-header mb-0 py-3 text-center text-nowrap  ${currentBotType === 'custom-bot-without-proxy' ? 'bg-primary text-light' : ''}`}>
+                <span className="mr-2">
+                  {t('admin:slack_integration.selecting_bot_types.custom_bot')}
+                </span>
+                <span className="supplementary-desc mr-2">
+                  {t('admin:slack_integration.selecting_bot_types.without_proxy')}
+                </span>
+                {/* TODO: add an appropriate link by GW-5614 */}
+                <i
+                  className={`fa fa-external-link btn-link ${currentBotType === 'custom-bot-without-proxy' ? 'bg-primary text-light' : ''}`}
+                  aria-hidden="true"
+                >
+                </i>
+              </h3>
+              <div className="card-body p-4">
+                <p className="card-text">
+                  <div className="text-center">
+                    {showBotTypeLevel('for_intermediate')}
+                  </div>
+                  <div className="my-4">
+                    <div className="d-flex justify-content-between mb-2">
+                      {showBotTypeLabel('set_up')}
+                      {showBotTypeDiscription('normal')}
+                    </div>
+                    <div className="d-flex justify-content-between mb-2">
+                      {showBotTypeLabel('multiple_workspaces_integration')}
+                      {showBotTypeDiscription('impossible')}
+                    </div>
+                    <div className="d-flex justify-content-between">
+                      {showBotTypeLabel('security_control')}
+                      {showBotTypeDiscription('possible')}
+                    </div>
+                  </div>
+                </p>
+              </div>
             </div>
-          </div>
 
-          <div
-            className={`card admin-bot-card mx-3 py-5 rounded ${currentBotType === 'custom-bot-with-proxy' ? 'border-info' : ''}`}
-            onClick={() => handleBotTypeSelect('custom-bot-with-proxy')}
-          >
-            <div className="card-body">
-              <h5 className="card-title">Custom Bot (With Proxy)</h5>
-              <p className="card-text">This is a wider card with supporting text below as a natural lead-in to additional content.</p>
+            <div
+              className={`card admin-bot-card mx-3 rounded shadow ${currentBotType === 'custom-bot-with-proxy' ? 'border-primary' : ''}`}
+              onClick={() => handleBotTypeSelect('custom-bot-with-proxy')}
+              role="button"
+            >
+              <h3 className={`card-header mb-0 py-3 text-center text-nowrap ${currentBotType === 'custom-bot-with-proxy' ? 'bg-primary text-light' : ''}`}>
+                <span className="mr-2">
+                  {t('admin:slack_integration.selecting_bot_types.custom_bot')}
+                </span>
+                <span className="supplementary-desc mr-2">
+                  {t('admin:slack_integration.selecting_bot_types.with_proxy')}
+                </span>
+                {/* TODO: add an appropriate link by GW-5614 */}
+                <i
+                  className={`fa fa-external-link btn-link ${currentBotType === 'custom-bot-with-proxy' ? 'bg-primary text-light' : ''}`}
+                  aria-hidden="true"
+                >
+                </i>
+              </h3>
+              <div className="card-body p-4">
+                <p className="card-text">
+                  <div className="text-center">
+                    {showBotTypeLevel('for_advanced')}
+                  </div>
+                  <div className="my-4">
+                    <div className="d-flex justify-content-between mb-2">
+                      {showBotTypeLabel('set_up')}
+                      {showBotTypeDiscription('hard')}
+                    </div>
+                    <div className="d-flex justify-content-between mb-2">
+                      {showBotTypeLabel('multiple_workspaces_integration')}
+                      {showBotTypeDiscription('possible')}
+                    </div>
+                    <div className="d-flex justify-content-between">
+                      {showBotTypeLabel('security_control')}
+                      {showBotTypeDiscription('impossible')}
+                    </div>
+                  </div>
+                </p>
+              </div>
             </div>
-          </div>
 
+          </div>
         </div>
       </div>
 

+ 20 - 2
src/client/styles/scss/_admin.scss

@@ -83,8 +83,26 @@
     }
   }
 
-  .admin-bot-card {
-    cursor: pointer;
+  /*
+  Slack Integration
+  */
+  .selecting-bot-type {
+    .btn-link {
+      font-size: 1rem;
+    }
+    .supplementary-desc {
+      font-size: 1rem;
+    }
+    .badge-info {
+      font-size: 0.6rem;
+    }
+    .admin-bot-card {
+      min-width: 280px;
+      border-radius: 8px !important;
+    }
+    .border-primary {
+      border-width: 2px;
+    }
   }
 
   //// TODO: migrate to Bootstrap 4

+ 21 - 0
src/client/styles/scss/theme/_apply-colors.scss

@@ -591,3 +591,24 @@ mark.rbt-highlight-text {
 .grw-btn-page-management:focus {
   background-color: rgba($color-link, 0.15);
 }
+
+/*
+  Slack Integration
+*/
+.selecting-bot-type {
+  .bot-type-disc-easy {
+    color: #33d541;
+  }
+  .bot-type-disc-normal {
+    color: #e6a63c;
+  }
+  .bot-type-disc-hard {
+    color: #ff5757;
+  }
+  .bot-type-disc-possible {
+    color: $info;
+  }
+  .bot-type-disc-impossible {
+    color: $gray-500;
+  }
+}

+ 39 - 3
src/server/routes/apiv3/slack-bot.js

@@ -1,6 +1,9 @@
 
 const express = require('express');
 
+const crypto = require('crypto');
+const qs = require('qs');
+
 const loggerFactory = require('@alias/logger');
 
 const logger = loggerFactory('growi:routes:apiv3:slack-bot');
@@ -10,7 +13,6 @@ const router = express.Router();
 module.exports = (crowi) => {
   this.app = crowi.express;
 
-
   // Check if the access token is correct
   function verificationAccessToken(req, res, next) {
     const slackBotAccessToken = req.body.slack_bot_access_token || null;
@@ -32,7 +34,41 @@ module.exports = (crowi) => {
     return next();
   }
 
-  router.post('/', verificationRequestUrl, verificationAccessToken, async(req, res) => {
+  /**
+   * Verify if the request came from slack
+   * See: https://api.slack.com/authentication/verifying-requests-from-slack
+   */
+  // TODO GW-5628 move this to slack package
+  function verifyingIsSlackRequest(req, res, next) {
+    // Temporary
+    req.signingSecret = crowi.configManager.getConfig('crowi', 'slackbot:signingSecret');
+
+    // take out slackSignature and timestamp from header
+    const slackSignature = req.headers['x-slack-signature'];
+    const timestamp = req.headers['x-slack-request-timestamp'];
+
+    // protect against replay attacks
+    const time = Math.floor(new Date().getTime() / 1000);
+    if (Math.abs(time - timestamp) > 300) {
+      return res.send('Verification failed.');
+    }
+
+    // generate growi signature
+    const sigBaseString = `v0:${timestamp}:${qs.stringify(req.body, { format: 'RFC1738' })}`;
+    const hasher = crypto.createHmac('sha256', req.signingSecret);
+    hasher.update(sigBaseString, 'utf8');
+    const hashedSigningSecret = hasher.digest('hex');
+    const growiSignature = `v0=${hashedSigningSecret}`;
+
+    // compare growiSignature and slackSignature
+    if (crypto.timingSafeEqual(Buffer.from(growiSignature, 'utf8'), Buffer.from(slackSignature, 'utf8'))) {
+      return next();
+    }
+
+    return res.send('Verification failed');
+  }
+
+  router.post('/', verificationRequestUrl, verifyingIsSlackRequest, verificationAccessToken, async(req, res) => {
 
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
@@ -94,7 +130,7 @@ module.exports = (crowi) => {
     }
   };
 
-  router.post('/interactive', verificationRequestUrl, async(req, res) => {
+  router.post('/interactive', verificationRequestUrl, verifyingIsSlackRequest, async(req, res) => {
 
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events

+ 50 - 0
src/server/routes/apiv3/slack-integration.js

@@ -4,6 +4,7 @@ const logger = loggerFactory('growi:routes:apiv3:notification-setting');
 const express = require('express');
 const { body } = require('express-validator');
 const crypto = require('crypto');
+const { WebClient, LogLevel } = require('@slack/web-api');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 const router = express.Router();
@@ -55,6 +56,9 @@ module.exports = (crowi) => {
       body('currentBotType')
         .isIn(['official-bot', 'custom-bot-without-proxy', 'custom-bot-with-proxy']),
     ],
+    NotificationTestToSlackWorkSpace: [
+      body('channel').isString(),
+    ],
   };
 
   async function updateSlackBotSettings(params) {
@@ -268,6 +272,52 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /slack-integration/test-notification-to-slack-work-space:
+   *      post:
+   *        tags: [SlackTestToWorkSpace]
+   *        operationId: postSlackMessageToSlackWorkSpace
+   *        summary: test to send message to slack work space
+   *        description: post message to slack work space
+   *        responses:
+   *          200:
+   *            description: Succeeded to send a message to slack work space
+   */
+  router.post('/notification-test-to-slack-work-space',
+    loginRequiredStrictly, adminRequired, csrf, validator.NotificationTestToSlackWorkSpace, apiV3FormValidator, async(req, res) => {
+      const isConnectedToSlack = crowi.slackBotService.isConnectedToSlack;
+      const { channel } = req.body;
+
+      if (isConnectedToSlack === false) {
+        const msg = 'Bot User OAuth Token is not setup.';
+        logger.error('Error', msg);
+        return res.apiv3Err(new ErrorV3(msg, 'not-setup-slack-bot-token', 400));
+      }
+
+      const slackBotToken = crowi.configManager.getConfig('crowi', 'slackbot:token');
+      this.client = new WebClient(slackBotToken, { logLevel: LogLevel.DEBUG });
+      logger.debug('SlackBot: setup is done');
+
+      try {
+        this.client.chat.postMessage({
+          channel: `#${channel}`,
+          text: 'Your test was successful!',
+        });
+        logger.info(`SlackTest: send success massage to slack work space at #${channel}.`);
+        logger.info(`If you do not receive a message, you may not have invited the bot to the #${channel} channel.`);
+        // eslint-disable-next-line max-len
+        const message = `Successfully send message to Slack work space. See #general channel. If you do not receive a message, you may not have invited the bot to the #${channel} channel.`;
+        return res.apiv3({ message });
+      }
+      catch (error) {
+        const msg = 'Error occured in testing to notify slack work space';
+        logger.error('Error', error);
+        return res.apiv3Err(new ErrorV3(msg, 'notification-test-slack-work-space-failed'), 500);
+      }
+    });
+
   /**
    * @swagger
    *

+ 38 - 0
tsconfig.base.json

@@ -0,0 +1,38 @@
+{
+  "compilerOptions": {
+    "target": "es2019",
+    "module": "commonjs",
+    "lib": ["dom", "dom.iterable", "esnext"],
+    "sourceMap": true,
+    "noEmit": true,
+    "removeComments": false,
+    "importHelpers": true,
+    "isolatedModules": true,
+
+    /* Strict Type-Checking Options */
+    // "strict": true,
+    "strictNullChecks": true,
+    "noImplicitAny": false,
+
+    /* Additional Checks */
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+
+    /* Module Resolution Options */
+    "moduleResolution": "node",
+    "typeRoots": [
+      "./node_modules/@types"
+    ],
+    "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
+
+    /* Misc */
+    "preserveConstEnums": true,
+    "forceConsistentCasingInFileNames": true,
+    "resolveJsonModule": true,
+
+    /* Experimental Options */
+    "experimentalDecorators": true,
+    "emitDecoratorMetadata": true
+  }
+}

Разница между файлами не показана из-за своего большого размера
+ 881 - 34
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов