diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d2d6c66 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.{js,ts}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..8b84b0f --- /dev/null +++ b/.eslintrc @@ -0,0 +1,30 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier/@typescript-eslint", + "plugin:prettier/recommended" + ], + "env": { + "node": true, + "es6": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 2015, + "ecmaFeatures": { + "modules": true + } + }, + "rules": { + "prettier/prettier": "error", + "indent": 0, + "camelcase": 0, + "@typescript-eslint/camelcase": 0, + "@typescript-eslint/explicit-function-return-type": "off" + }, + "plugins": [ + "@typescript-eslint" + ] + } \ No newline at end of file diff --git a/.gitignore b/.gitignore index e9ea765..b74515d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules -config.js +config/index.ts storage \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4cc587f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4672b28 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "eslint.autoFixOnSave": true, + "eslint.validate": [ + "javascript", + { + "language": "typescript", + "autoFix": true + }, + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index d0da94f..f3e4007 100644 --- a/Makefile +++ b/Makefile @@ -4,5 +4,5 @@ start: serveo: npm run serveo -format: - npm run format \ No newline at end of file +lint: + npm run lint \ No newline at end of file diff --git a/kubernetes/deployment.yaml b/kubernetes/deployment.yaml index 4d99277..d0dba3f 100644 --- a/kubernetes/deployment.yaml +++ b/kubernetes/deployment.yaml @@ -53,9 +53,9 @@ spec: image: gcr.io/htn-techyon/mentorship-slackbot volumeMounts: - name: mentorship-slackbot-secret - mountPath: /srv/config.js + mountPath: /srv/src/config/index.ts readOnly: true - subPath: config.js + subPath: config.ts - name: mentorship-slackbot-disk mountPath: /srv/storage resources: diff --git a/package-lock.json b/package-lock.json index 66585e9..f0092c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,11 +4,36 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, "@slack/events-api": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@slack/events-api/-/events-api-2.2.0.tgz", - "integrity": "sha512-3PDcBSq0vfu8rnNmz+GYjzzuuKs4E5Tj7sZzLlOdzjXvgbvb0Q8VqgSCzRk9+9ZTImrjliac41KMo2HYHW+57w==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@slack/events-api/-/events-api-2.3.0.tgz", + "integrity": "sha512-Chsp2+zVneo0Y7CU/RsqxS7Z1SAoEjMJJ2/K83gN6VHjeCigATF2A8mLQXXqmjUFaH4sHgTRaxjzhCfhFrqrBA==", "requires": { + "@types/debug": "^4.1.4", + "@types/express": "^4.17.0", + "@types/lodash.isstring": "^4.0.6", + "@types/node": ">=4.2.0", + "@types/yargs": "^13.0.0", "debug": "^2.6.1", "express": "^4.0.0", "lodash.isstring": "^4.0.1", @@ -17,26 +42,6 @@ "yargs": "^6.6.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -44,99 +49,20 @@ "requires": { "ms": "2.0.0" } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "requires": { - "lcid": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "yargs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^4.2.0" - } - }, - "yargs-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", - "requires": { - "camelcase": "^3.0.0" - } } } }, "@slack/interactive-messages": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@slack/interactive-messages/-/interactive-messages-1.1.1.tgz", - "integrity": "sha512-FIyj/sBmCjQ4B4Tt1z6MNdFmgvyT5CK2U2VrWGcdqdynxQk8tlN/AA/C0SSc5wIr/WNYck1c90ZawQJTlw3YsQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@slack/interactive-messages/-/interactive-messages-1.3.0.tgz", + "integrity": "sha512-AMFQYuS2RTywTNAz8XqEdKpZTKozrS6BCtFoYHfJbxqsQQb7WnnxvdWxWmVk9Ih82+ASrJNlg/INVC7tu1lBIA==", "requires": { + "@types/debug": "^4.1.4", + "@types/express": "^4.17.0", + "@types/lodash.isfunction": "^3.0.6", + "@types/lodash.isregexp": "^4.0.6", + "@types/lodash.isstring": "^4.0.6", + "@types/node": ">=4.2.0", "axios": "^0.18.0", "debug": "^3.1.0", "lodash.isfunction": "^3.0.9", @@ -156,9 +82,9 @@ } }, "@slack/types": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-1.0.0.tgz", - "integrity": "sha512-IktC4uD/CHfLQcSitKSmjmRu4a6+Nf/KzfS6dTgUlDzENhh26l8aESKAuIpvYD5VOOE6NxDDIAdPJOXBvUGxlg==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-1.2.1.tgz", + "integrity": "sha512-NyGh7MibW+a0OHkwwOPlv63hC639dzQdkFy4dj0tl4sAdZR4OBaE/OhXixKhCzcT3kJsPXaQmvkUN7sqSf52iA==" }, "@slack/web-api": { "version": "5.0.1", @@ -179,6 +105,58 @@ "p-retry": "^4.0.0" } }, + "@types/body-parser": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", + "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/connect": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "requires": { + "@types/node": "*" + } + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.1.tgz", + "integrity": "sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.9.tgz", + "integrity": "sha512-GqpaVWR0DM8FnRUJYKlWgyARoBUAVfRIeVDZQKOttLFp5SmhhF9YFIYeTPwMd/AXfxlP7xVO2dj1fGu0Q+krKQ==", + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, "@types/form-data": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", @@ -195,21 +173,181 @@ "@types/node": "*" } }, + "@types/json-schema": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", + "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.144", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.144.tgz", + "integrity": "sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg==" + }, + "@types/lodash.isfunction": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.isfunction/-/lodash.isfunction-3.0.6.tgz", + "integrity": "sha512-olhgKmBgzHnA5pxsOI6YHunzTBMSyBw1XjxIKFio8W+XhYiELGTt05FStE0suV0GWtlIMdn7V8M/UbYbSVdGYw==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.isregexp": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.isregexp/-/lodash.isregexp-4.0.6.tgz", + "integrity": "sha512-+MxWeayd6d0xdLuLA6T3kY7uNdfHH82m4gsxgpcQkpHcqEmXJiTWDrfUhDdlmErAn6tlj5zhjKsV0mQEkfJcfA==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.isstring": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.isstring/-/lodash.isstring-4.0.6.tgz", + "integrity": "sha512-uUGvF9G1G7jQ5H42Y38GA9rZmUoY8wI/OMSwnW0BZA+Ra0uxzpuQf4CixXl3yG3TvF6LjuduMyt1WvKl+je8QA==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/lowdb": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/lowdb/-/lowdb-1.0.9.tgz", + "integrity": "sha512-LBRG5EPXFOJDoJc9jACstMhtMP+u+UkPYllBeGQXXKiaHc+uzJs9+/Aynb/5KkX33DtrIiKyzNVTPQc/4RcD6A==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" + }, "@types/node": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", - "integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==" + "version": "12.7.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", + "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==" }, "@types/p-queue": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/p-queue/-/p-queue-2.3.2.tgz", "integrity": "sha512-eKAv5Ql6k78dh3ULCsSBxX6bFNuGjTmof5Q/T6PiECDq0Yf8IIn46jCyp3RJvCi8owaEmm3DZH1PEImjBMd/vQ==" }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "@types/request": { + "version": "2.48.3", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.3.tgz", + "integrity": "sha512-3Wo2jNYwqgXcIz/rrq18AdOZUQB8cQ34CXZo+LUwPJNpvRAL86+Kc2wwI8mqpz9Cr1V+enIox5v+WZhy/p3h8w==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } + } + }, "@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, + "@types/serve-static": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, + "@types/yargs": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", + "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==" + }, + "@typescript-eslint/eslint-plugin": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.3.3.tgz", + "integrity": "sha512-12cCbwu5PbQudkq2xCIS/QhB7hCMrsNPXK+vJtqy/zFqtzVkPRGy12O5Yy0gUK086f3VHV/P4a4R4CjMW853pA==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "2.3.3", + "eslint-utils": "^1.4.2", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^2.0.1", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.3.3.tgz", + "integrity": "sha512-MQ4jKPMTU1ty4TigJCRKFPye2qyQdH8jzIIkceaHgecKFmkNS1hXPqKiZ+mOehkz6+HcN5Nuvwm+frmWZR9tdg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.3.3", + "eslint-scope": "^5.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.3.3.tgz", + "integrity": "sha512-+cV53HuYFeeyrNW8x/rgPmbVrzzp/rpRmwbJnNtwn4K8mroL1BdjxwQh7X9cUHp9rm4BBiEWmD3cSBjKG7d5mw==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.3.3", + "@typescript-eslint/typescript-estree": "2.3.3", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.3.3.tgz", + "integrity": "sha512-GkACs12Xp8d/STunNv/iSMYJFQrkrax9vuPZySlgSzoJJtw1cp6tbEw4qsLskQv6vloLrkFJHcTJ0a/yCB5cIA==", + "dev": true, + "requires": { + "glob": "^7.1.4", + "is-glob": "^4.0.1", + "lodash.unescape": "4.0.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -219,6 +357,18 @@ "negotiator": "0.6.2" } }, + "acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "dev": true + }, + "acorn-jsx": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", + "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==", + "dev": true + }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -230,6 +380,41 @@ "uri-js": "^4.2.2" } }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "arg": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", + "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -248,6 +433,12 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -272,6 +463,12 @@ "is-buffer": "^2.0.2" } }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -307,21 +504,137 @@ } } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -330,6 +643,12 @@ "delayed-stream": "~1.0.0" } }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -358,6 +677,19 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -375,9 +707,9 @@ }, "dependencies": { "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -386,6 +718,12 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -401,6 +739,20 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==" + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -415,6 +767,12 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -433,6 +791,170 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.5.1.tgz", + "integrity": "sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.2", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.4.1", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.4.0.tgz", + "integrity": "sha512-YrKucoFdc7SEko5Sxe4r6ixqXPDP1tunGw91POeZTTRKItf/AMFYt/YLEQtZMkR2LVpAVhcAcZgcWpm1oGPW7w==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-plugin-prettier": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.1.tgz", + "integrity": "sha512-A+TZuHZ0KU0cnn56/9mfR7/KjUJ9QNVXUhwvRFSR7PGPe0zQR6PTkmyqg1AtUUEOzTqeRsUwyKFh0oVZKVCrtA==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", + "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.0.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz", + "integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-jsx": "^5.0.2", + "eslint-visitor-keys": "^1.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -495,6 +1017,17 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -505,11 +1038,41 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -534,6 +1097,32 @@ } } }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, "follow-redirects": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", @@ -577,11 +1166,29 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -590,6 +1197,35 @@ "assert-plus": "^1.0.0" } }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", @@ -609,10 +1245,16 @@ "har-schema": "^2.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==" }, "http-errors": { "version": "1.7.2", @@ -644,11 +1286,69 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", + "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", @@ -664,6 +1364,27 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -684,11 +1405,33 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -704,6 +1447,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -720,6 +1469,24 @@ "verror": "1.10.0" } }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -732,14 +1499,6 @@ "strip-bom": "^2.0.0" }, "dependencies": { - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -772,6 +1531,12 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, + "lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", + "dev": true + }, "lowdb": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz", @@ -784,6 +1549,11 @@ "steno": "^0.4.1" } }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -817,6 +1587,36 @@ "mime-db": "1.40.0" } }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, "moniker": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/moniker/-/moniker-0.1.2.tgz", @@ -827,11 +1627,29 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -861,6 +1679,52 @@ "ee-first": "1.1.1" } }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, "p-queue": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-2.4.2.tgz", @@ -875,11 +1739,48 @@ "retry": "^0.12.0" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -930,12 +1831,33 @@ "pinkie": "^2.0.0" } }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, "prettier": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", "dev": true }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "proxy-addr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", @@ -976,6 +1898,16 @@ "unpipe": "1.0.0" } }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", @@ -983,37 +1915,14 @@ "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - } } }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", @@ -1059,18 +1968,61 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, "resolve": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", - "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "requires": { "path-parse": "^1.0.6" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, "retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1149,6 +2101,52 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -1173,9 +2171,15 @@ } }, "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "sshpk": { "version": "1.16.1", @@ -1206,6 +2210,44 @@ "graceful-fs": "^4.1.3" } }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -1214,6 +2256,67 @@ "is-utf8": "^0.2.0" } }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -1235,11 +2338,37 @@ } } }, + "ts-node": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.4.1.tgz", + "integrity": "sha512-5LpRN+mTiCs7lI5EtbXmF/HfMeCjzt7DH9CZwtkr6SywStrNQC723wG+aOWFiLNn7zT3kD/RnFqi3ZUfr4l5Qw==", + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, "tsscmp": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1253,6 +2382,15 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1262,6 +2400,11 @@ "mime-types": "~2.1.24" } }, + "typescript": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", + "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1285,6 +2428,12 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -1309,6 +2458,26 @@ "extsprintf": "^1.2.0" } }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -1350,6 +2519,92 @@ } } } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^4.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "yargs-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "requires": { + "camelcase": "^3.0.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } } } diff --git a/package.json b/package.json index 3153b41..e8dca56 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "## General overview", "main": "index.js", "scripts": { - "format": "prettier --write src/**/*.js", - "start": "node src/server.js", - "serveo": "ssh -R test-mentorship:80:localhost:3000 serveo.net" + "start": "ts-node src/server.js", + "serveo": "ssh -R test-mentorship:80:localhost:3000 serveo.net", + "lint": "eslint --ext .jsx,.js,.tsx,.ts src/ --fix" }, "repository": { "type": "git", @@ -19,16 +19,28 @@ }, "homepage": "https://github.com/hackathon/mentorship-slackbot#readme", "devDependencies": { + "@types/lowdb": "^1.0.9", + "@typescript-eslint/eslint-plugin": "^2.3.3", + "@typescript-eslint/parser": "^2.3.3", + "eslint": "^6.5.1", + "eslint-config-prettier": "^6.4.0", + "eslint-plugin-prettier": "^3.1.1", "prettier": "^1.18.2" }, "dependencies": { - "@slack/events-api": "^2.2.0", - "@slack/interactive-messages": "^1.1.1", + "@slack/events-api": "^2.3.0", + "@slack/interactive-messages": "^1.3.0", "@slack/web-api": "^5.0.1", + "@types/express": "^4.17.1", + "@types/node": "^12.7.12", + "@types/request": "^2.48.3", "body-parser": "^1.19.0", "express": "^4.17.1", "lowdb": "^1.0.0", "moniker": "^0.1.2", - "request": "^2.88.0" + "request": "^2.88.0", + "ts-node": "^8.4.1", + "tslib": "^1.10.0", + "typescript": "^3.6.4" } } diff --git a/src/actions/message.js b/src/actions/message.ts similarity index 51% rename from src/actions/message.js rename to src/actions/message.ts index cc50804..c8d0299 100644 --- a/src/actions/message.js +++ b/src/actions/message.ts @@ -1,19 +1,50 @@ -const { web } = require("../clients"); +import config from "config"; -const db = require("../db"); +import { webClient } from "clients"; +import * as db from "db"; +import Text from "text"; +import { + ActiveSession, + isClaimed, + TS, + Session, + ChannelID, + ClaimedSession, + UserID, + Skills +} from "typings"; +import { ChatUpdateArguments, ChatPostMessageArguments } from "@slack/web-api"; -const { - BOT_USERNAME, - SKILLS, - CHANNEL_ID, - STATS_CHANNEL_ID -} = require("../../config"); +export const { chat, dialog } = webClient; -const Text = require("../text"); +export const MENTOR_GROUP_CHANNEL = config.CHANNEL_ID; -const mentor_group_channel = CHANNEL_ID; +type Context = + | "deleted" + | "canceled" + | "mentee" + | "mentee_completed" + | "intro" + | null; -const buildMentorRequestActions = (session, context) => { +function postMessage(value: ChatPostMessageArguments) { + return chat.postMessage({ + as_user: true, + username: config.BOT_USERNAME, + ...value + }); +} +function update(value: ChatUpdateArguments) { + return chat.update({ + as_user: true, + ...value + }); +} + +export const buildMentorRequestActions = ( + session: ActiveSession, + context: Context +) => { switch (context) { case "deleted": return [ @@ -72,7 +103,7 @@ const buildMentorRequestActions = (session, context) => { return []; case null: return [ - session.mentor != null + isClaimed(session) ? { type: "context", elements: [ @@ -112,55 +143,63 @@ const buildMentorRequestActions = (session, context) => { } }; -const buildMentorRequest = (session, context = null) => { +export const buildMentorRequest = ( + session: ActiveSession, + context: Context = null +) => { const actions = buildMentorRequestActions(session, context); - let mentors = []; + let mentors: string[] = []; if (context == null && session.submission.skill != null) { const allMentors = db.getMentors(); mentors = Object.keys(allMentors).filter( m => allMentors[m].skills[session.submission.skill] === true ); } - return [ - { - type: "divider" - }, - { - type: "context", - block_id: "mentor_request", - elements: [ - { + + const title = Text.MENTOR_REQUEST_TITLE( + session.username, + session.submission, + mentors + ); + const details = Text.MENTOR_REQUEST_DETAILS(session.submission); + + return { + text: title + " " + details, + blocks: [ + { + type: "divider" + }, + { + type: "context", + block_id: "mentor_request", + elements: [ + { + type: "mrkdwn", + text: title + } + ] + }, + { + type: "section", + text: { type: "mrkdwn", - text: Text.MENTOR_REQUEST_TITLE( - session.username, - session.submission, - mentors - ) + text: details } - ] - }, - { - type: "section", - text: { - type: "mrkdwn", - text: Text.MENTOR_REQUEST_DETAILS(session.submission) - } - }, - ...actions - ]; + }, + ...actions + ] + }; }; -const postMentorRequest = session => { - return web.chat.postMessage({ - channel: mentor_group_channel, - blocks: buildMentorRequest(session), - as_user: true, - username: BOT_USERNAME +export const postMentorRequest = (session: ActiveSession) => { + return postMessage({ + channel: MENTOR_GROUP_CHANNEL, + ...buildMentorRequest(session) }); }; -const openMentorRequestDialog = (trigger_id, ts) => { - web.dialog.open({ +export const openMentorRequestDialog = (trigger_id: string, ts: TS) => { + dialog.open({ trigger_id, dialog: { callback_id: "mentor_request", @@ -180,8 +219,8 @@ const openMentorRequestDialog = (trigger_id, ts) => { name: "skill", type: "select", optional: true, - options: Object.keys(SKILLS).map(value => ({ - label: SKILLS[value], + options: Object.keys(config.SKILLS).map(value => ({ + label: config.SKILLS[value], value })) }, @@ -196,42 +235,40 @@ const openMentorRequestDialog = (trigger_id, ts) => { }); }; -const confirmMentorRequest = session => { - web.chat.update({ +export const confirmMentorRequest = (session: ActiveSession) => { + update({ channel: session.channel, ts: session.mentee_ts, - blocks: buildMentorRequest(session, "mentee"), - as_user: true, - username: BOT_USERNAME + ...buildMentorRequest(session, "mentee") }); }; -const bumpMentorRequest = session => { - web.chat +export const bumpMentorRequest = (session: ActiveSession) => { + chat .getPermalink({ - channel: mentor_group_channel, + channel: MENTOR_GROUP_CHANNEL, message_ts: session.ts }) .then(({ permalink }) => { - web.chat.postMessage({ - channel: mentor_group_channel, + const text = Text.BUMP(permalink as string, session); + postMessage({ + channel: MENTOR_GROUP_CHANNEL, + text, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.BUMP(permalink, session) + text } } ], - unfurl_links: true, - as_user: true, - username: BOT_USERNAME + unfurl_links: true }); }); }; -const requestActionBlock = { +export const requestActionBlock = { type: "actions", elements: [ { @@ -246,16 +283,17 @@ const requestActionBlock = { ] }; -const welcome = session => { - web.chat.postMessage({ +export const welcome = (session: Session) => { + const text = Text.WELCOME(session.name); + postMessage({ channel: session.channel, - text: Text.WELCOME(session.name), + text, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.WELCOME(session.name) + text } }, { @@ -266,62 +304,53 @@ const welcome = session => { } }, requestActionBlock - ], - as_user: true, - username: BOT_USERNAME + ] }); }; -const noSession = ({ channel }) => { - web.chat - .postMessage({ - channel, - text: Text.NO_SESSION, - blocks: [ - { - type: "section", - text: { type: "mrkdwn", text: Text.NO_SESSION } - }, - requestActionBlock - ], - as_user: true, - username: BOT_USERNAME - }) - .catch(console.error); +export const noSession = ({ channel }: Session) => { + const text = Text.NO_SESSION; + postMessage({ + channel, + text, + blocks: [ + { + type: "section", + text: { type: "mrkdwn", text } + }, + requestActionBlock + ] + }).catch(console.error); }; -const noUnderstand = ({ channel }) => { - web.chat - .postMessage({ - channel, - blocks: [ - { - type: "section", - text: { type: "mrkdwn", text: Text.NO_UNDERSTAND } - } - ], - as_user: true, - username: BOT_USERNAME - }) - .catch(console.error); +export const noUnderstand = ({ channel }: Session) => { + const text = Text.NO_UNDERSTAND; + postMessage({ + channel, + text, + blocks: [ + { + type: "section", + text: { type: "mrkdwn", text } + } + ] + }).catch(console.error); }; -const noUnderstandMentor = channel => { - web.chat - .postMessage({ - channel, - blocks: [ - { - type: "section", - text: { type: "mrkdwn", text: Text.NO_UNDERSTAND_MENTOR } - } - ], - as_user: true, - username: BOT_USERNAME - }) - .catch(console.error); +export const noUnderstandMentor = (channel: ChannelID) => { + const text = Text.NO_UNDERSTAND_MENTOR; + postMessage({ + channel, + text, + blocks: [ + { + type: "section", + text: { type: "mrkdwn", text } + } + ] + }).catch(console.error); }; -const needMentor = ({ channel }) => { - web.chat.postMessage({ +export const needMentor = ({ channel }: Session) => { + postMessage({ channel: channel, text: Text.NEED_MENTOR, blocks: [ @@ -333,47 +362,47 @@ const needMentor = ({ channel }) => { } }, requestActionBlock - ], - as_user: true, - username: BOT_USERNAME + ] }); }; -const sessionIntroduction = session => { +export const sessionIntroduction = (session: ClaimedSession) => { + const { blocks } = buildMentorRequest(session, "intro"); + const introText = Text.SESSION_INTRODUCTION(session); // update the new channel - web.chat.postMessage({ + postMessage({ channel: session.group_id, + text: introText, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.SESSION_INTRODUCTION(session) + text: introText } }, - ...buildMentorRequest(session, "intro") - ], - as_user: true + ...blocks + ] }); // update the existing user message - web.chat.update({ - channel: mentor_group_channel, + update({ + channel: MENTOR_GROUP_CHANNEL, ts: session.ts, - blocks: buildMentorRequest(session), - as_user: true, - username: BOT_USERNAME + ...buildMentorRequest(session) }); // let the mentor know - return web.chat.postMessage({ + const mentorText = Text.SESSION_CLAIMED(session); + return postMessage({ channel: session.mentor, + text: mentorText, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.SESSION_CLAIMED(session) + text: mentorText } }, { @@ -410,240 +439,209 @@ const sessionIntroduction = session => { } ] } - ], - as_user: true + ] }); }; -const sessionSurrendered = (session, newSession) => { - web.chat.update({ +export const sessionSurrendered = ( + session: ClaimedSession, + newSession: ActiveSession +) => { + const mentorText = Text.SESSION_SURRENDERED_MENTOR; + update({ channel: session.mentor_channel, ts: session.mentor_claim_ts, + text: mentorText, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.SESSION_SURRENDERED_MENTOR + text: mentorText } } - ], - as_user: true + ] }); - web.chat.update({ - channel: mentor_group_channel, + update({ + channel: MENTOR_GROUP_CHANNEL, ts: session.ts, - blocks: buildMentorRequest(newSession), - as_user: true, - username: BOT_USERNAME + ...buildMentorRequest(newSession) }); - web.chat + chat .getPermalink({ - channel: mentor_group_channel, + channel: MENTOR_GROUP_CHANNEL, message_ts: session.ts }) .then(({ permalink }) => { - web.chat.postMessage({ - channel: mentor_group_channel, + const text = Text.BUMP_SURRENDER(permalink as string, session); + postMessage({ + channel: MENTOR_GROUP_CHANNEL, + text, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.BUMP_SURRENDER(permalink, session) + text } } ], - unfurl_links: true, - as_user: true, - username: BOT_USERNAME + unfurl_links: true }); }); - return web.chat.postMessage({ + const text = Text.SESSION_SURRENDERED(session); + return postMessage({ channel: session.group_id, + text, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.SESSION_SURRENDERED(session) + text } } - ], - as_user: true + ] }); }; -const sessionCompleted = session => { - web.chat.update({ +export const sessionCompleted = (session: ClaimedSession) => { + const mentorText = Text.SESSION_COMPLETED_MENTOR; + update({ channel: session.mentor_channel, ts: session.mentor_claim_ts, + text: mentorText, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.SESSION_COMPLETED_MENTOR + text: mentorText } } - ], - as_user: true + ] }); - web.chat.update({ + update({ channel: session.channel, ts: session.mentee_ts, - blocks: buildMentorRequest(session, "mentee_completed"), - as_user: true, - username: BOT_USERNAME + ...buildMentorRequest(session, "mentee_completed") }); noSession(session); - return web.chat.postMessage({ + const text = Text.SESSION_COMPLETED(session); + return postMessage({ channel: session.group_id, + text, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.SESSION_COMPLETED(session) + text } } - ], - as_user: true + ] }); }; -const postSessionCanceled = session => { - web.chat.update({ +export const postSessionCanceled = (session: ActiveSession) => { + update({ channel: session.channel, ts: session.mentee_ts, - blocks: buildMentorRequest(session, "canceled"), - as_user: true, - username: BOT_USERNAME + ...buildMentorRequest(session, "canceled") }); - web.chat.update({ - channel: mentor_group_channel, + update({ + channel: MENTOR_GROUP_CHANNEL, ts: session.ts, - blocks: buildMentorRequest(session, "canceled"), - as_user: true, - username: BOT_USERNAME + ...buildMentorRequest(session, "canceled") }); }; -const postSessionDeleted = session => { - const { channel, mentor_channel, mentee_ts, mentor_claim_ts, ts } = session; - web.chat.update({ - channel: mentor_group_channel, +export const postSessionDeleted = (session: ActiveSession) => { + const { channel, mentee_ts, ts } = session; + update({ + channel: MENTOR_GROUP_CHANNEL, ts: ts, - blocks: buildMentorRequest(session, "deleted"), - as_user: true, - username: BOT_USERNAME + ...buildMentorRequest(session, "deleted") }); - if (mentor_claim_ts != null) { - web.chat.update({ - channel: mentor_channel, - ts: mentor_claim_ts, + if (isClaimed(session)) { + const mentorText = Text.SESSION_DELETED_MENTOR; + update({ + channel: session.mentor_channel, + ts: session.mentor_claim_ts, + text: mentorText, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.SESSION_DELETED_MENTOR + text: mentorText } } - ], - as_user: true + ] }); } - web.chat.update({ + chat.update({ channel, ts: mentee_ts, - blocks: buildMentorRequest(session, "deleted"), - as_user: true, - username: BOT_USERNAME + ...buildMentorRequest(session, "deleted") }); - web.chat - .postMessage({ - channel, - text: Text.SESSION_DELETED, - blocks: [ - { - type: "section", - text: { - type: "mrkdwn", - text: Text.SESSION_DELETED - } + const text = Text.SESSION_DELETED; + postMessage({ + channel, + text, + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text } - ], - as_user: true - }) - .then(() => needMentor(session)); + } + ] + }).then(() => needMentor(session)); }; -const skillsHelp = channel => { - web.chat - .postMessage({ - channel, - blocks: [ - { - type: "section", - text: { type: "mrkdwn", text: Text.SKILLS_HELP(Object.keys(SKILLS)) } - } - ], - as_user: true, - username: BOT_USERNAME - }) - .catch(console.error); +export const skillsHelp = (channel: ChannelID) => { + const text = Text.SKILLS_HELP(Object.keys(config.SKILLS)); + postMessage({ + channel, + text, + blocks: [ + { + type: "section", + text: { type: "mrkdwn", text } + } + ] + }).catch(console.error); }; -const skillsSet = (channel, newSkills) => { - web.chat - .postMessage({ - channel, - blocks: [ - { - type: "section", - text: { type: "mrkdwn", text: Text.SKILLS_SET(newSkills) } - } - ], - as_user: true, - username: BOT_USERNAME - }) - .catch(console.error); +export const skillsSet = (channel: UserID, newSkills: Skills) => { + const text = Text.SKILLS_SET(newSkills); + postMessage({ + channel, + text, + blocks: [ + { + type: "section", + text: { type: "mrkdwn", text } + } + ] + }).catch(console.error); }; -const stats = stats => { - web.chat.postMessage({ - channel: STATS_CHANNEL_ID, +export const stats = (stats: { created: number; online: number }) => { + const text = Text.STATS(stats); + postMessage({ + channel: config.STATS_CHANNEL_ID, + text, blocks: [ { type: "section", text: { type: "mrkdwn", - text: Text.STATS(stats) + text } } - ], - as_user: true, - username: BOT_USERNAME + ] }); }; - -module.exports = { - welcome, - needMentor, - noSession, - noUnderstand, - noUnderstandMentor, - openMentorRequestDialog, - confirmMentorRequest, - postMentorRequest, - postSessionDeleted, - postSessionCanceled, - sessionIntroduction, - sessionSurrendered, - sessionCompleted, - bumpMentorRequest, - skillsHelp, - skillsSet, - stats -}; diff --git a/src/actions/timed.js b/src/actions/timed.ts similarity index 67% rename from src/actions/timed.js rename to src/actions/timed.ts index 056733b..a7e4340 100644 --- a/src/actions/timed.js +++ b/src/actions/timed.ts @@ -1,16 +1,15 @@ -const db = require("../db"); +import * as db from "db"; +import { runnable, interval } from "date"; -const { rescan } = require("./users"); -const message = require("./message"); - -const { runnable, interval } = require("../date"); +import { rescan } from "./users"; +import * as message from "./message"; const bumpSessions = () => { const sessions = db.getSessionsToBump(); sessions.map(message.bumpMentorRequest); }; -const stats = () => { +export const stats = () => { if (!runnable()) return; const created = db.getCreated(); const online = db.getOnline(); @@ -20,7 +19,7 @@ const stats = () => { }); }; -const init = () => { +export const init = () => { // bump dead requests every 30s interval(bumpSessions, 30000); bumpSessions(); @@ -32,8 +31,5 @@ const init = () => { // send stats every 3 hours interval(stats, 1000 * 60 * 60 * 3); }; -module.exports = { - init, - runnable, - stats -}; + +export { runnable }; diff --git a/src/actions/users.js b/src/actions/users.js deleted file mode 100644 index 13e5127..0000000 --- a/src/actions/users.js +++ /dev/null @@ -1,99 +0,0 @@ -const { CHANNEL_ID } = require("../../config"); - -const { web } = require("../clients"); - -const { welcome } = require("./message"); - -const db = require("../db"); - -const { runnable } = require("../date"); - -const tryWelcome = (member, session, mentorChannelIds, canWelcome) => { - if (!session.welcomed && (canWelcome || mentorChannelIds.has(member.id))) { - welcome( - db.updateSession(member.id, { - welcomed: true - }) - ); - } -}; - -// tries to add a member to our index -const tryAdd = (member, mentorChannelIds, canWelcome) => { - const session = db.getSession(member.id); - if (!member.is_bot && session == null) { - web.im.open({ user: member.id }).then(({ channel }) => { - tryWelcome( - member, - db.updateSession(member.id, { - id: member.id, - channel: channel.id, - name: member.name, - welcomed: false - }), - mentorChannelIds, - canWelcome - ); - }); - } else if (session != null) { - tryWelcome(member, session, mentorChannelIds, canWelcome); - } -}; - -const updateMentors = (members, mentorChannelIds) => { - const existingMentors = db.getMentors(); - const mentors = {}; - for (const member of members) { - if (mentorChannelIds.has(member.id)) { - mentors[member.id] = existingMentors[member.id] || { - skills: {} - }; - } - } - Promise.all(Object.keys(mentors).map(user => web.users.getPresence({ user }))) - .then( - results => results.filter(({ presence }) => presence === "active").length - ) - .then(db.setOnline); - db.setMentors(mentors); -}; - -const rescan = () => { - const getMembers = (channel, cursor = undefined) => { - return web.conversations - .members({ channel, cursor, limit: 500 }) - .then(({ members, response_metadata: { next_cursor } }) => { - if (next_cursor === "") { - return members; - } else { - return getMentors(channel, next_cursor).then(nextMembers => { - return [...members, ...nextMembers]; - }); - } - }); - }; - const getAll = (cursor = undefined) => { - return web.users - .list({ cursor, limit: 500 }) - .then(({ members, response_metadata: { next_cursor } }) => { - const filtered_members = members.filter(m => !m.deleted); - if (next_cursor === "") { - return filtered_members; - } else { - return getAll(next_cursor).then(nextMembers => { - return [...filtered_members, ...nextMembers]; - }); - } - }); - }; - Promise.all([getAll(), getMembers(CHANNEL_ID)]).then( - ([members, _mentorChannelIds]) => { - const mentorChannelIds = new Set(_mentorChannelIds); - updateMentors(members, mentorChannelIds); - const canWelcome = runnable(); - members.forEach(member => tryAdd(member, mentorChannelIds, canWelcome)); - } - ); -}; - -module.exports = { rescan, tryAdd }; diff --git a/src/actions/users.ts b/src/actions/users.ts new file mode 100644 index 0000000..7fa7394 --- /dev/null +++ b/src/actions/users.ts @@ -0,0 +1,125 @@ +import config from "config"; + +import { webClient } from "clients"; +import * as db from "db"; + +import { runnable } from "date"; +import { welcome } from "actions/message"; + +import { Session, UserID, ChannelID } from "typings"; + +interface Member { + deleted: boolean; + id: UserID; + is_bot: boolean; + name: string; +} + +const tryWelcome = ( + member: Member, + session: Session, + mentorChannelIds: Set, + canWelcome: boolean +) => { + if (!session.welcomed && (canWelcome || mentorChannelIds.has(member.id))) { + welcome( + db.updateSession(member.id, { + welcomed: true + }) + ); + } +}; + +// tries to add a member to our index +export const tryAdd = ( + member: Member, + mentorChannelIds: Set, + canWelcome: boolean +) => { + const session = db.getSession(member.id); + if (!member.is_bot && session == null) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + webClient.im.open({ user: member.id }).then(({ channel }: any) => { + tryWelcome( + member, + db.updateSession(member.id, { + id: member.id, + channel: channel.id, + name: member.name, + welcomed: false + }), + mentorChannelIds, + canWelcome + ); + }); + } else if (session != null) { + tryWelcome(member, session, mentorChannelIds, canWelcome); + } +}; + +const updateMentors = (members: Member[], mentorChannelIds: Set) => { + const existingMentors = db.getMentors(); + const mentors = {}; + for (const member of members) { + if (mentorChannelIds.has(member.id)) { + mentors[member.id] = existingMentors[member.id] || { + skills: {} + }; + } + } + Promise.all( + Object.keys(mentors).map(user => webClient.users.getPresence({ user })) + ) + .then( + results => results.filter(({ presence }) => presence === "active").length + ) + .then(db.setOnline); + db.setMentors(mentors); +}; + +export const rescan = () => { + const getMembers = ( + channel: ChannelID, + cursor: string | undefined = undefined + ): Promise => { + return webClient.conversations + .members({ channel, cursor, limit: 500 }) + .then(({ members, response_metadata }) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { next_cursor } = response_metadata!; + if (next_cursor === "") { + return members as UserID[]; + } else { + return getMembers(channel, next_cursor).then(nextMembers => { + return [...(members as UserID[]), ...nextMembers]; + }); + } + }); + }; + const getAll = ( + cursor: string | undefined = undefined + ): Promise => { + return webClient.users + .list({ cursor, limit: 500 }) + .then(({ members, response_metadata }) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { next_cursor } = response_metadata!; + const filtered_members = (members as Member[]).filter(m => !m.deleted); + if (next_cursor === "") { + return filtered_members; + } else { + return getAll(next_cursor).then(nextMembers => { + return [...filtered_members, ...nextMembers]; + }); + } + }); + }; + Promise.all([getAll(), getMembers(config.CHANNEL_ID)]).then( + ([members, _mentorChannelIds]) => { + const mentorChannelIds = new Set(_mentorChannelIds); + updateMentors(members, mentorChannelIds); + const canWelcome = runnable(); + members.forEach(member => tryAdd(member, mentorChannelIds, canWelcome)); + } + ); +}; diff --git a/src/clients.js b/src/clients.js deleted file mode 100644 index f23c07e..0000000 --- a/src/clients.js +++ /dev/null @@ -1,11 +0,0 @@ -const { WebClient } = require("@slack/web-api"); -const { createEventAdapter } = require("@slack/events-api"); -const { createMessageAdapter } = require("@slack/interactive-messages"); - -const { SIGNING_SECRET, BOT_ACCESS_TOKEN } = require("../config"); - -const events = createEventAdapter(SIGNING_SECRET); -const interactions = createMessageAdapter(SIGNING_SECRET); -const web = new WebClient(BOT_ACCESS_TOKEN); - -module.exports = { events, web, interactions }; diff --git a/src/clients.ts b/src/clients.ts new file mode 100644 index 0000000..d5fad7a --- /dev/null +++ b/src/clients.ts @@ -0,0 +1,9 @@ +import { WebClient } from "@slack/web-api"; +import { createEventAdapter } from "@slack/events-api"; +import { createMessageAdapter } from "@slack/interactive-messages"; + +import config from "config"; + +export const eventsClient = createEventAdapter(config.SIGNING_SECRET); +export const interactionsClient = createMessageAdapter(config.SIGNING_SECRET); +export const webClient = new WebClient(config.BOT_ACCESS_TOKEN); diff --git a/src/actions/channel.js b/src/config/.gitkeep similarity index 100% rename from src/actions/channel.js rename to src/config/.gitkeep diff --git a/src/date.js b/src/date.js deleted file mode 100644 index 3de1708..0000000 --- a/src/date.js +++ /dev/null @@ -1,17 +0,0 @@ -const start = new Date("2019-09-13T21:00:00-0400"); -const end = new Date("2019-09-15T12:00:00-0400"); - -const runnable = () => { - const date = new Date(); - const output = start <= date && date <= end; - if (!output) { - console.log(`${start} <= ${date} && ${date} <= ${end} is false, not running`); - } - return output; -}; - -const interval = (fn, ms) => setInterval(() => { - if (new Date() <= end) fn(); -}, ms); - -module.exports = {runnable, interval}; \ No newline at end of file diff --git a/src/date.ts b/src/date.ts new file mode 100644 index 0000000..5b2204f --- /dev/null +++ b/src/date.ts @@ -0,0 +1,18 @@ +const start = new Date("2019-09-13T21:00:00-0400"); +const end = new Date("2019-09-15T12:00:00-0400"); + +export const runnable = () => { + const date = new Date(); + const output = start <= date && date <= end; + if (!output) { + console.log( + `${start} <= ${date} && ${date} <= ${end} is false, not running` + ); + } + return output; +}; + +export const interval = (fn: () => void, ms: number) => + setInterval(() => { + if (new Date() <= end) fn(); + }, ms); diff --git a/src/db.js b/src/db.js deleted file mode 100644 index 917a735..0000000 --- a/src/db.js +++ /dev/null @@ -1,107 +0,0 @@ -const low = require("lowdb"); -const FileSync = require("lowdb/adapters/FileSync"); - -const adapter = new FileSync("storage/db.json"); -const db = low(adapter); - -if (db.get("sessions").value() == null) { - db.set("sessions", {}).write(); -} - -const getSession = user => - db - .get("sessions") - .get(user) - .value(); - -const getSessionsToBump = () => { - const sessions = db.get("sessions").filter(session => - session.submission != null && - session.mentor == null && - new Date(session.last_updated).getTime() < new Date().getTime() - (1000 * 60 * 10) - ).value(); - for (const session of sessions) { - db.get("sessions").get(session.id).set("last_updated", new Date().toString()).write(); - } - return sessions; -} - -const updateSession = (user, newSession) => { - const session = {...(getSession(user) || {}), ...newSession, id: user, last_updated: new Date().toString()}; - return db.get("sessions").set(user, session).write()[user]; -} - -const clearSession = user => - db - .get("sessions") - .get(user) - .set("ts", undefined) - .set("mentor", undefined) - .set("mentor_claim_ts", undefined) - .set("group_id", undefined) - .write(); - -const getUserIdByThreadTs = threadTs => - db - .get("sessions") - .findKey(channel => channel.ts === threadTs) - .value(); - -const getMentors = () => - db - .get("mentors") - .value() || {}; - -const setMentors = (mentors) => - db - .set("mentors", mentors) - .write(); - -const getMentor = (user) => - db - .get("mentors") - .get(user) - .value(); - -const setMentorSkills = (user, skills) => - db - .get("mentors") - .get(user) - .set("skills", skills) - .write(); - -const getOnline = () => - db - .get("online") - .value() || 0; - -const setOnline = (count) => - db - .set("online", count) - .write(); - -const getCreated = () => -db - .get("created") - .value() || 0; - -const bumpCreated = () => -db - .set("created", getCreated() + 1) - .write(); - -module.exports = { - getSession, - getSessionsToBump, - clearSession, - getUserIdByThreadTs, - updateSession, - getMentors, - setMentors, - getMentor, - setMentorSkills, - getOnline, - setOnline, - getCreated, - bumpCreated, -}; diff --git a/src/db.ts b/src/db.ts new file mode 100644 index 0000000..c81354a --- /dev/null +++ b/src/db.ts @@ -0,0 +1,114 @@ +/* eslint-disable @typescript-eslint/ban-ts-ignore */ +import low from "lowdb"; +import FileSync from "lowdb/adapters/FileSync"; +import { + Session, + UserID, + TS, + Mentor, + Skills, + isActive, + isClaimed, + ActiveSession +} from "typings"; + +const adapter = new FileSync("storage/db.json"); +const db = low(adapter); + +if (db.get("sessions").value() == null) { + db.set("sessions", {}).write(); +} + +export const getSession = (user: UserID): Session => + db + .get("sessions") + // @ts-ignore + .get(user) + .value(); + +export const getSessionsToBump = () => { + const sessions = db + .get("sessions") + // @ts-ignore + .filter( + (session: Session) => + isActive(session) && + !isClaimed(session) && + new Date(session.last_updated).getTime() < + new Date().getTime() - 1000 * 60 * 10 + ) + .value(); + for (const session of sessions) { + db.get("sessions") + // @ts-ignore + .get(session.id) + .set("last_updated", new Date().toString()) + .write(); + } + return sessions; +}; + +export const updateSession = ( + user: UserID, + newSession: Partial +): Session => { + const session = { + ...(getSession(user) || {}), + ...newSession, + id: user, + // eslint-disable-next-line @typescript-eslint/camelcase + last_updated: new Date().toString() + }; + return ( + db + .get("sessions") + .set(user, session) + // @ts-ignore + .write()[user] + ); +}; + +export const clearSession = (user: UserID) => + db + .get("sessions") + // @ts-ignore + .get(user) + .set("ts", undefined) + .set("mentor", undefined) + .set("mentor_claim_ts", undefined) + .set("group_id", undefined) + .write(); + +export const getUserIdByThreadTs = (threadTs: TS): UserID | undefined => + db + .get("sessions") + .findKey((session: ActiveSession) => session.ts === threadTs) + .value(); + +export const getMentors = () => db.get("mentors").value() || {}; + +export const setMentors = (mentors: { [key: string]: Mentor }) => + db.set("mentors", mentors).write(); + +export const getMentor = (user: UserID) => + db + .get("mentors") + // @ts-ignore + .get(user) + .value(); + +export const setMentorSkills = (user: UserID, skills: Skills) => + db + .get("mentors") + // @ts-ignore + .get(user) + .set("skills", skills) + .write(); + +export const getOnline = () => db.get("online").value() || 0; + +export const setOnline = (count: number) => db.set("online", count).write(); + +export const getCreated = () => db.get("created").value() || 0; + +export const bumpCreated = () => db.set("created", getCreated() + 1).write(); diff --git a/src/events/interactions.js b/src/events/interactions.js deleted file mode 100644 index c1f8bcf..0000000 --- a/src/events/interactions.js +++ /dev/null @@ -1,115 +0,0 @@ -const Text = require("../text"); - -const message = require("../actions/message"); - -const { web } = require("../clients"); - -const { - bumpCreated, - getSession, - clearSession, - getUserIdByThreadTs, - updateSession -} = require("../db"); - -const handleNeedMentor = (payload, respond) => { - // check for existing session - const session = getSession(payload.user.id); - if (session.ts != null) { - respond({ - text: Text.SESSION_ALREADY_ACTIVE - }); - } else { - // send problem prompt text - message.openMentorRequestDialog(payload.trigger_id, payload.message.ts); - } -}; - -const handleMentorRequest = payload => { - const { user, channel, submission, state } = payload; - const session = updateSession(user.id, { - username: user.name, - channel: channel.id, - mentee_ts: state, - submission - }); - message - .postMentorRequest(session) - .then(({ ts }) => - message.confirmMentorRequest(updateSession(user.id, { ts })) - ); - bumpCreated(); -}; - -const handleCancelRequest = ({ user: { id } }, respond) => { - const session = getSession(id); - message.postSessionCanceled(session); - clearSession(id); - message.needMentor(session); - - // respond in private mentor channel -}; - -const handleClaimRequest = payload => { - const userId = getUserIdByThreadTs(payload.message.ts); - const session = updateSession(userId, { mentor: payload.user.id }); - - web.conversations - .open({ - users: [session.id, session.mentor].join(",") - }) - .then(response => { - const groupId = response.channel.id; - message - .sessionIntroduction(updateSession(session.id, { group_id: groupId })) - .then(({ ts, channel }) => - updateSession(userId, { - mentor_claim_ts: ts, - mentor_channel: channel - }) - ); - }); -}; - -const handleDeleteRequest = payload => { - const userId = payload.actions[0].value; - const session = getSession(userId); - message.postSessionDeleted(session); - clearSession(userId); -}; - -const handleSurrenderRequest = payload => { - const userId = payload.actions[0].value; - const session = getSession(userId); - message.sessionSurrendered( - session, - updateSession(userId, { - mentor_claim_ts: undefined, - group_id: undefined, - mentor: undefined - }) - ); -}; - -const handleCompleteRequest = payload => { - const userId = payload.actions[0].value; - const session = getSession(userId); - message.sessionCompleted(session).then(() => { - clearSession(userId); - }); -}; - -const bootstrap = interactions => { - interactions.action({ actionId: "need_mentor" }, handleNeedMentor); - interactions.action({ callbackId: "mentor_request" }, handleMentorRequest); - interactions.action({ actionId: "cancel_request" }, handleCancelRequest); - interactions.action({ actionId: "claim_request" }, handleClaimRequest); - interactions.action({ actionId: "delete_request" }, handleDeleteRequest); - interactions.action( - { actionId: "surrender_request" }, - handleSurrenderRequest - ); - interactions.action({ actionId: "complete_request" }, handleCompleteRequest); -}; - -module.exports = { bootstrap }; diff --git a/src/events/interactions.ts b/src/events/interactions.ts new file mode 100644 index 0000000..5bea1a3 --- /dev/null +++ b/src/events/interactions.ts @@ -0,0 +1,161 @@ +import SlackMessageAdapter from "@slack/interactive-messages/dist/adapter"; + +import Text from "text"; +import * as message from "actions/message"; +import { webClient } from "clients"; + +import { + bumpCreated, + getSession, + clearSession, + getUserIdByThreadTs, + updateSession +} from "db"; + +import { + UserID, + ChannelID, + Submission, + TS, + coerceActive, + coerceClaimed, + ActiveSession, + ClaimedSession, + isActive +} from "typings"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Respond = (message: any) => Promise; +interface ActionPayload { + channel: { + id: ChannelID; + }; + trigger_id: string; + user: { + name: string; + id: UserID; + }; +} +interface MessagePayload extends ActionPayload { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actions: any; + message: { + ts: string; + }; +} +interface DialogPayload extends ActionPayload { + state: string; + submission: Submission; +} + +const handleNeedMentor = (payload: MessagePayload, respond: Respond) => { + // check for existing session + const session = getSession(payload.user.id); + if (isActive(session)) { + respond({ + text: Text.SESSION_ALREADY_ACTIVE + }); + } else { + // send problem prompt text + message.openMentorRequestDialog(payload.trigger_id, payload.message.ts); + } +}; + +const handleMentorRequest = (payload: DialogPayload) => { + const { user, channel, submission, state } = payload; + const session = updateSession(user.id, { + username: user.name, + channel: channel.id, + mentee_ts: state, + submission + }); + message + .postMentorRequest(session as ActiveSession) + .then(({ ts }) => + message.confirmMentorRequest( + coerceActive(updateSession(user.id, { ts: ts as TS })) + ) + ); + bumpCreated(); +}; + +const handleCancelRequest = ({ user: { id } }: MessagePayload) => { + const session = coerceActive(getSession(id), true); + message.postSessionCanceled(session); + clearSession(id); + message.needMentor(session); +}; + +const handleClaimRequest = (payload: MessagePayload) => { + const userId = getUserIdByThreadTs(payload.message.ts); + + if (userId === undefined) + throw new Error(`Undefined user_id for ts '${payload.message.ts}'`); + + const session = updateSession(userId, { + mentor: payload.user.id + }) as ClaimedSession; + + webClient.conversations + .open({ + users: [session.id, session.mentor].join(",") + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .then(({ channel: { id } }: any) => { + message + .sessionIntroduction(updateSession(session.id, { + group_id: id + }) as ClaimedSession) + .then(({ ts, channel }) => + coerceClaimed( + updateSession(userId, { + mentor_claim_ts: ts as TS, + mentor_channel: channel as ChannelID + }) + ) + ); + }); +}; + +const handleDeleteRequest = (payload: MessagePayload) => { + const userId = payload.actions[0].value; + const session = coerceActive(getSession(userId), true); + message.postSessionDeleted(session); + clearSession(userId); +}; + +const handleSurrenderRequest = (payload: MessagePayload) => { + const userId = payload.actions[0].value; + const session = coerceClaimed(getSession(userId)); + message.sessionSurrendered( + session, + coerceActive( + updateSession(userId, { + mentor_claim_ts: undefined, + group_id: undefined, + mentor: undefined + }) + ) + ); +}; + +const handleCompleteRequest = (payload: MessagePayload) => { + const userId = payload.actions[0].value; + const session = coerceClaimed(getSession(userId)); + message.sessionCompleted(session).then(() => { + clearSession(userId); + }); +}; + +export const bootstrap = (interactions: SlackMessageAdapter) => { + interactions.action({ actionId: "need_mentor" }, handleNeedMentor); + interactions.action({ callbackId: "mentor_request" }, handleMentorRequest); + interactions.action({ actionId: "cancel_request" }, handleCancelRequest); + interactions.action({ actionId: "claim_request" }, handleClaimRequest); + interactions.action({ actionId: "delete_request" }, handleDeleteRequest); + interactions.action( + { actionId: "surrender_request" }, + handleSurrenderRequest + ); + interactions.action({ actionId: "complete_request" }, handleCompleteRequest); +}; diff --git a/src/events/message.js b/src/events/message.ts similarity index 86% rename from src/events/message.js rename to src/events/message.ts index f8d9016..31ab47a 100644 --- a/src/events/message.js +++ b/src/events/message.ts @@ -1,8 +1,8 @@ -const { SKILLS } = require("../../config"); +import config from "config"; -const db = require("../db"); -const message = require("../actions/message"); -const timed = require("../actions/timed"); +import db from "db"; +import message from "actions/message"; +import timed from "../actions/timed"; const messageHandler = event => { // ignore bot messages @@ -29,7 +29,7 @@ const messageHandler = event => { } else { const skillsObj = {}; for (const part of parts) { - if (part in SKILLS) { + if (part in config.SKILLS) { skillsObj[part] = true; } } @@ -57,4 +57,4 @@ const messageHandler = event => { } }; -module.exports = messageHandler; +export const handle = messageHandler; diff --git a/src/server.js b/src/server.js deleted file mode 100644 index 363cd5d..0000000 --- a/src/server.js +++ /dev/null @@ -1,60 +0,0 @@ -const http = require("http"); -const bodyParser = require("body-parser"); -const express = require("express"); -const request = require('request'); - -const port = process.env.PORT || 3000; - -const { events, interactions } = require("./clients"); - -const { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET } = require("../config"); - -const app = express(); - -app.get('/auth', function(req, res){ - if (!req.query.code) { // access denied - return; - } - var data = {form: { - client_id: SLACK_CLIENT_ID, - client_secret: SLACK_CLIENT_SECRET, - code: req.query.code - }}; - request.post('https://slack.com/api/oauth.access', data, function (error, response, body) { - if (!error && response.statusCode == 200) { - // Get an auth token - let oauthToken = JSON.parse(body).access_token; - - console.log(body); - // OAuth done- redirect the user to wherever - res.redirect(__dirname + "/public/success.html"); - } - }) -}); - -// Plug the event adapter into the express app as middleware -app.use("/slack/events", events.expressMiddleware()); - -// Plug the interactions adapter into the express app as middleware -require("./events/interactions").bootstrap(interactions); -app.use("/slack/actions", interactions.expressMiddleware()); - -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); - -// Attach listeners to events by Slack Event "type". See: https://api.slack.com/events/message.im -events.on("message", event => require("./events/message")(event)); -// events.on('app_mention', require('./events/message')); -// events.on('message.im', require('./events/message')); -// events.on('message.group', console.log); - -// Handle errors (see `errorCodes` export) -events.on("error", console.error); - -// Start a basic HTTP server -http.createServer(app).listen(port, () => { - // Listening on path '/slack/events' by default - console.log(`Server listening on port ${port}`); -}); - -require("./actions/timed").init(); \ No newline at end of file diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..9767b93 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,69 @@ +import http from "http"; +import bodyParser from "body-parser"; +import express from "express"; +import request from "request"; + +const port = process.env.PORT || 3000; + +import { eventsClient, interactionsClient } from "clients"; + +import interactions from "events/interactions"; +import message from "events/message"; + +import config from "config"; + +const app = express(); + +app.get("/auth", function(req, res) { + if (!req.query.code) { + // access denied + return; + } + const data = { + form: { + client_id: config.SLACK_CLIENT_ID, + client_secret: config.SLACK_CLIENT_SECRET, + code: req.query.code + } + }; + request.post("https://slack.com/api/oauth.access", data, function( + error, + response, + body + ) { + if (!error && response.statusCode == 200) { + // Get an auth token + res.json({ + success: true, + result: body + }); + } else { + res.json({ + success: false, + error + }); + } + }); +}); + +// Plug the event adapter into the express app as middleware +app.use("/slack/events", eventsClient.expressMiddleware()); + +// Plug the interactions adapter into the express app as middleware +interactions.bootstrap(interactionsClient); +app.use("/slack/actions", interactionsClient.expressMiddleware()); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); + +eventsClient.on("message", event => message.handle(event)); +eventsClient.on("error", console.error); + +// Start a basic HTTP server +http.createServer(app).listen(port, () => { + // Listening on path '/slack/events' by default + console.log(`Server listening on port ${port}`); +}); + +import timed from "actions/timed"; +timed.init(); diff --git a/src/text.js b/src/text.js deleted file mode 100644 index 18e029e..0000000 --- a/src/text.js +++ /dev/null @@ -1,69 +0,0 @@ -const { SKILLS } = require("../config"); - -const db = require("./db"); - -module.exports = { - WELCOME: name => - `Hi ${name ? name : "hacker"}, welcome to Hack the North 2019! :wave: - -Iā€™m Mentorship Bot, here to help you -find a mentor at any time during the -event.`, - BUMP: (permalink, session) => `<@${session.id}>'s <${permalink}|mentorship request${session.submission.skill != null ? ` regarding ${session.submission.skill}` : ''}> has not received any attention recently. Please take a look!`, - BUMP_SURRENDER: (permalink, session) => `<@${session.id}>'s <${permalink}|mentorship request${session.submission.skill != null ? ` regarding ${session.submission.skill}` : ''}> *was surrendered*. Someone please take a look!`, - NEED_MENTOR: "Need a mentor? Simply click the button below.", - NEED_MENTOR_BUTTON: "I need a mentor", - NO_SESSION: "You do not currently have a mentorship session active.", - NO_UNDERSTAND: "Sorry, I am not equipped to deal with messages.", - NO_UNDERSTAND_MENTOR: "Sorry, I don't understand your message. Try `!skills help` to see and set your skills", - REQUEST_CONFIRM: "*Your request has been sent to mentors*", - CANCEL_REQUEST_BUTTON: "Cancel Request", - MENTOR_REQUEST_CLAIMED: claimer => `Request claimed by <@${claimer}>`, - MENTOR_REQUEST_CANCELED: "*Request canceled by user*", - MENTOR_REQUEST_DELETED: "*Request has been deleted*", - MENTOR_REQUEST_TITLE: (user, { location, skill }, mentors) => - `Mentorship request from @${user}\nLocation: ${location}${skill != null ? `\nCategory: ${SKILLS[skill]} ${mentors.length > 0 ? `(${mentors.map(m => `<@${m}>`).join(', ')})` : ''}` : ''}`, - MENTOR_REQUEST_DETAILS: ({ description }) => description, - MENTOR_REQUEST_CONFIRM: "Claim", - MENTOR_REQUEST_DELETE: "Delete", - MENTOR_REQUEST_SURRENDER: "Surrender", - MENTOR_REQUEST_COMPLETE: 'Mark Complete', - MENTEE_MESSAGE_NOTIF: username => - `Your request has been submitted @${username}!`, - SESSION_ALREADY_ACTIVE: "You already have an active mentorship session", - SESSION_CLAIMED: ({id}) => - `Thank you for claiming <@${id}>'s request ļøā¤ļø. Please use this message to manage this session.`, - SESSION_DELETED: - "Your mentorship request was deleted by a mentor - if you believe this was a mistake, please make a new request or contact our mentorship lead @bonnie", - SESSION_DELETED_MENTOR: "This mentorship request has been deleted", - SESSION_INTRODUCTION: (session) => `šŸ‘‹ Hello <@${session.id}>! Your session has been claimed by <@${session.mentor}>, our resident expert of Hacking, Northing, and everything in-between. As a reminder, your request was as follows:`, - SESSION_SURRENDERED_MENTOR: `You have surrendered the session`, - SESSION_SURRENDERED: (session) => `<@${session.mentor}> has surrendered - your request will be placed back in the pool of active mentor requests`, - SESSION_COMPLETED_MENTOR: `Your session has has been marked complete`, - SESSION_COMPLETED_MENTEE: `*Your request has been marked complete*`, - SESSION_COMPLETED: (session) => `<@${session.mentor}> has marked this session as completed. Archiving this channel šŸ˜Ž`, - SKILLS_HELP: (skills) => - `Set your skills so we can notify you when a relevant request comes in! - -You can set your skills with the command -\`\`\` -!skills [skill1] [skill2] ... -\`\`\` - -*The available skills are:* - -${skills.join("\n")}`, - SKILLS_SET: (skills) => `You have successfully set your skills to -\`\`\` -${skills.length > 0 ? `[\n ${skills.join(",\n ")}\n]` : `[None]`} -\`\`\` -`, - STATS: (stats) => `Hello there! Your friendly neighbourhood mentorship bot reporting in šŸ˜Š - -So far, we have: -šŸ‘‹ ${stats.created} mentor requests created -šŸ˜Š ${stats.online} mentor${stats.online === 1 ? '' : 's'} online - -Need a mentor? Send me a message and we'll get you help ASAP. Alternatively, feel free to drop by the mentorship hub in the E7 CND (check the map)! -` -}; diff --git a/src/text.ts b/src/text.ts new file mode 100644 index 0000000..3cb71a1 --- /dev/null +++ b/src/text.ts @@ -0,0 +1,104 @@ +import config from "config"; +import { + UserID, + Submission, + UserName, + ActiveSession, + ClaimedSession, + Skills +} from "./typings"; + +export default { + WELCOME: (name?: string) => + `Hi ${name ? name : "hacker"}, welcome to Hack the North 2019! :wave: + +Iā€™m Mentorship Bot, here to help you +find a mentor at any time during the +event.`, + BUMP: (permalink: string, session: ActiveSession) => + `<@${session.id}>'s <${permalink}|mentorship request${ + session.submission.skill != null + ? ` regarding ${session.submission.skill}` + : "" + }> has not received any attention recently. Please take a look!`, + BUMP_SURRENDER: (permalink: string, session: ActiveSession) => + `<@${session.id}>'s <${permalink}|mentorship request${ + session.submission.skill != null + ? ` regarding ${session.submission.skill}` + : "" + }> *was surrendered*. Someone please take a look!`, + NEED_MENTOR: "Need a mentor? Simply click the button below.", + NEED_MENTOR_BUTTON: "I need a mentor", + NO_SESSION: "You do not currently have a mentorship session active.", + NO_UNDERSTAND: "Sorry, I am not equipped to deal with messages.", + NO_UNDERSTAND_MENTOR: + "Sorry, I don't understand your message. Try `!skills help` to see and set your skills", + REQUEST_CONFIRM: "*Your request has been sent to mentors*", + CANCEL_REQUEST_BUTTON: "Cancel Request", + MENTOR_REQUEST_CLAIMED: (claimer: UserID) => + `Request claimed by <@${claimer}>`, + MENTOR_REQUEST_CANCELED: "*Request canceled by user*", + MENTOR_REQUEST_DELETED: "*Request has been deleted*", + MENTOR_REQUEST_TITLE: ( + user: string, + { location, skill }: Submission, + mentors: UserID[] + ) => + `Mentorship request from @${user}\nLocation: ${location}${ + skill != null + ? `\nCategory: ${config.SKILLS[skill]} ${ + mentors.length > 0 + ? `(${mentors.map(m => `<@${m}>`).join(", ")})` + : "" + }` + : "" + }`, + MENTOR_REQUEST_DETAILS: ({ description }: Submission) => description, + MENTOR_REQUEST_CONFIRM: "Claim", + MENTOR_REQUEST_DELETE: "Delete", + MENTOR_REQUEST_SURRENDER: "Surrender", + MENTOR_REQUEST_COMPLETE: "Mark Complete", + MENTEE_MESSAGE_NOTIF: (username: UserName) => + `Your request has been submitted @${username}!`, + SESSION_ALREADY_ACTIVE: "You already have an active mentorship session", + SESSION_CLAIMED: ({ id }: ActiveSession) => + `Thank you for claiming <@${id}>'s request ļøā¤ļø. Please use this message to manage this session.`, + SESSION_DELETED: + "Your mentorship request was deleted by a mentor - if you believe this was a mistake, please make a new request or contact our mentorship lead @bonnie", + SESSION_DELETED_MENTOR: "This mentorship request has been deleted", + SESSION_INTRODUCTION: (session: ClaimedSession) => + `šŸ‘‹ Hello <@${session.id}>! Your session has been claimed by <@${session.mentor}>, our resident expert of Hacking, Northing, and everything in-between. As a reminder, your request was as follows:`, + SESSION_SURRENDERED_MENTOR: `You have surrendered the session`, + SESSION_SURRENDERED: (session: ClaimedSession) => + `<@${session.mentor}> has surrendered - your request will be placed back in the pool of active mentor requests`, + SESSION_COMPLETED_MENTOR: `Your session has has been marked complete`, + SESSION_COMPLETED_MENTEE: `*Your request has been marked complete*`, + SESSION_COMPLETED: (session: ClaimedSession) => + `<@${session.mentor}> has marked this session as completed. Archiving this channel šŸ˜Ž`, + SKILLS_HELP: (skills: Skills) => + `Set your skills so we can notify you when a relevant request comes in! + +You can set your skills with the command +\`\`\` +!skills [skill1] [skill2] ... +\`\`\` + +*The available skills are:* + +${skills.join("\n")}`, + SKILLS_SET: (skills: Skills) => `You have successfully set your skills to +\`\`\` +${skills.length > 0 ? `[\n ${skills.join(",\n ")}\n]` : `[None]`} +\`\`\` +`, + STATS: (stats: { created: number; online: number }) => { + return `Hello there! Your friendly neighbourhood mentorship bot reporting in šŸ˜Š + +So far, we have: +šŸ‘‹ ${stats.created} mentor requests created +šŸ˜Š ${stats.online} mentor${stats.online === 1 ? "" : "s"} online + +Need a mentor? Send me a message and we'll get you help ASAP. Alternatively, feel free to drop by the mentorship hub in the E7 CND (check the map)! +`; + } +}; diff --git a/src/typings.ts b/src/typings.ts new file mode 100644 index 0000000..b603c78 --- /dev/null +++ b/src/typings.ts @@ -0,0 +1,80 @@ +export type Skill = string; +export type Skills = Skill[]; + +export type TS = string; + +export type UserID = string; +export type UserName = string; + +export type ChannelID = string; + +export interface EmptySession { + id: UserID; + channel: ChannelID; + name: string; + last_updated: string; + welcomed: boolean; +} + +export interface Submission { + location: string; + description: string; + skill: Skill; +} +export interface ActiveSession extends EmptySession { + username: UserName; + mentee_ts: TS; + ts: TS; + submission: Submission; +} +export interface ClaimedSession extends ActiveSession { + group_id: ChannelID; + mentor_claim_ts: TS; + mentor: UserID; + mentor_channel: ChannelID; +} + +export type Session = EmptySession | ActiveSession | ClaimedSession; +export function isActive(session: Session): session is ActiveSession { + // ts set last when transitioning empty -> active + return typeof (session as ActiveSession).ts === "string"; +} +export function isClaimed(session: Session): session is ClaimedSession { + // mentor_claim_ts set last when transitioning active -> claimed + return typeof (session as ClaimedSession).mentor_claim_ts === "string"; +} +export function isEmpty(session: Session): session is EmptySession { + return !isActive(session) && !isClaimed(session); +} + +export class SessionCoerceError extends Error { + constructor(type: string) { + super(`Failed to coerce session object to type '${type}'`); + Object.setPrototypeOf(this, new.target.prototype); + } +} +export function coerceActive( + session: Session, + allowClaimed = false +): ActiveSession { + if (isActive(session) && (allowClaimed || !isClaimed(session))) { + return session; + } + throw new SessionCoerceError("active"); +} +export function coerceClaimed(session: Session): ClaimedSession { + if (isClaimed(session)) { + return session; + } + throw new SessionCoerceError("claimed"); +} +export function coerceEmpty(session: Session): EmptySession { + if (isEmpty(session)) { + return session; + } + throw new SessionCoerceError("empty"); +} + +export interface Mentor { + skills: Skills; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..09e4234 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "outDir": "build/dist", + "module": "es2015", + "target": "es6", + "lib": ["es6"], + "sourceMap": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "isolatedModules": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["src"] + } \ No newline at end of file