commit 3a6f5f2d8e8e130c043be651c5f97df753e3bc61 Author: Evert Prants Date: Sat Nov 21 17:41:08 2020 +0200 hello world, squeebot 3 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e420693 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/dist/ +/lib/ +/node_modules/ +*.tsbuildinfo diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..177b617 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,404 @@ +{ + "name": "@squeebot/core", + "version": "3.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@types/dateformat": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-3.0.1.tgz", + "integrity": "sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==", + "dev": true + }, + "@types/fs-extra": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.4.tgz", + "integrity": "sha512-50GO5ez44lxK5MDH90DYHFFfqxH7+fTqEEnvguQRzJ/tY9qFrMSHLiYHite+F3SNmf7+LHC1eMXojuD+E3Qcyg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.9.tgz", + "integrity": "sha512-JsoLXFppG62tWTklIoO4knA+oDTYsmqWxHRvd4lpmfQRNhX6osheUOWETP2jMoV/2bEHuMra8Pp3Dmo/stBFcw==", + "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" + } + }, + "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" + } + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "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" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "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" + } + }, + "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 + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "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 + }, + "dateformat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.0.0.tgz", + "integrity": "sha512-zpKyDYpeePyYGJp2HhRxLHlA+jZQNjt+MwmcVmLxCIECeC4Ks3TI3yk/CSMKylbnCJ5htonfOugYtRRTpyoHow==" + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "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 + }, + "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 + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "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.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-core-module": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", + "integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "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.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } + } + }, + "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": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "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-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "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" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", + "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", + "dev": true + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..93dc361 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "@squeebot/core", + "version": "3.0.0", + "description": "Squeebot v3 core for the execution environment", + "main": "lib/index.js", + "module": "lib/", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc", + "watch": "tsc -w", + "prepare": "npm run build" + }, + "repository": { + "type": "git", + "url": "git+https://gitlab.icynet.eu/Squeebot/core.git" + }, + "author": "Evert \"Diamond\" Prants ", + "license": "MIT", + "devDependencies": { + "@types/dateformat": "^3.0.1", + "@types/fs-extra": "^9.0.4", + "@types/node": "^14.14.9", + "tslint": "^6.1.3", + "typescript": "^4.0.5" + }, + "dependencies": { + "dateformat": "^4.0.0", + "fs-extra": "^9.0.1" + } +} diff --git a/src/channel/index.ts b/src/channel/index.ts new file mode 100644 index 0000000..a38abb4 --- /dev/null +++ b/src/channel/index.ts @@ -0,0 +1,13 @@ +import { ScopedEventEmitter } from '../util/events'; + +export class ChannelManager { + constructor(private stream: ScopedEventEmitter) {} + + public initialize(configured: any[]): void { + for (const event of ['message', 'event', 'special']) { + this.stream.on('channel', event, (...data: any[]) => { + // TODO: pass messages between channels + }); + } + } +} diff --git a/src/core/environment.ts b/src/core/environment.ts new file mode 100644 index 0000000..a6657e9 --- /dev/null +++ b/src/core/environment.ts @@ -0,0 +1,42 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; + +import { IEnvironment } from '../types/environment'; + +const dirs: {[key: string]: string} = { + configurationPath: 'configs', + pluginsPath: 'plugins', + repositoryPath: 'repos', +}; + +export async function loadEnvironment(enviroFile: string = 'squeebot.env.json', chroot?: string): Promise { + if (!await fs.pathExists(enviroFile)) { + throw new Error('Environment file does not exist.'); + } + + const env = await fs.readJson(enviroFile); + if (!env.path && !chroot) { + throw new Error('Root path is unspecified.'); + } + + let root = env.path; + if (chroot) { + root = chroot; + env.path = chroot; + } + + if (!await fs.pathExists(root)) { + throw new Error('Root path does not exist.'); + } + + // Ensure necessary directories exist + for (const opt in dirs) { + if (!env[opt]) { + env[opt] = dirs[opt]; + } + env[opt] = path.resolve(root, dirs[opt]); + await fs.ensureDir(env[opt]); + } + + return env; +} diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 0000000..84616cc --- /dev/null +++ b/src/core/index.ts @@ -0,0 +1,2 @@ +export { loadEnvironment } from './environment'; +export { Logger, logger } from './logger'; diff --git a/src/core/logger.ts b/src/core/logger.ts new file mode 100644 index 0000000..c4cdde0 --- /dev/null +++ b/src/core/logger.ts @@ -0,0 +1,67 @@ +import dateFmt from 'dateformat'; +import util from 'util'; + +export class Logger { + public timestamp = 'dd/mm/yy HH:MM:ss'; + + constructor() {} + + private write(ltype: string, ...data: any[]): void { + const message = []; + let cfunc = console.log; + + if (this.timestamp) { + message.push(`[${dateFmt(new Date(), this.timestamp)}]`); + } + + switch (ltype) { + case 'info': + message.push('[ INFO]'); + break; + case 'error': + message.push('[ERROR]'); + cfunc = console.error; + break; + case 'warn': + message.push('[ WARN]'); + cfunc = console.warn; + break; + case 'debug': + message.push('[DEBUG]'); + break; + } + + // Short dance to apply formatting + let final = data[0]; + if (data.length > 1) { + const fargs = data.slice(1); + final = util.format(data[0], ...fargs); + } + message.push(final); + cfunc.apply(null, message); + } + + public log(...data: any[]): void { + this.write('info', ...data); + } + + public warn(...data: any[]): void { + this.write('warn', ...data); + } + + public info(...data: any[]): void { + this.write('info', ...data); + } + + public error(...data: any[]): void { + this.write('error', ...data); + } + + public debug(...data: any[]): void { + this.write('debug', ...data); + } +} + +const logger = new Logger(); + +export { logger }; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/npm/executor.ts b/src/npm/executor.ts new file mode 100644 index 0000000..12e5785 --- /dev/null +++ b/src/npm/executor.ts @@ -0,0 +1,69 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { IEnvironment } from '../types/environment'; +import { IProcessData, spawnProcess } from '../util/run'; + +export class NPMExecutor { + private installed: string[] = []; + private packageFile: string = path.join(this.environment.path, 'package.json'); + + constructor(private environment: IEnvironment, private coreModule: string) {} + + public async init(): Promise { + // Initialize npm environment + const c1 = await spawnProcess('npm', ['init', '-y'], this.environment); + if (c1.code > 0) { + throw new Error(c1.stderr.join('\n')); + } + + // Install core module + const c2 = await spawnProcess('npm', ['install', this.coreModule], this.environment); + if (c2.code > 0) { + throw new Error(c2.stderr.join('\n')); + } + } + + public async loadPackageFile(): Promise { + if (!await fs.pathExists(this.packageFile)) { + await this.init(); + } + + const jsonData = await fs.readJson(this.packageFile); + if (!jsonData.dependencies) { + return; + } + + this.installed = Object.keys(jsonData.dependencies); + } + + public async installPackage(pkg: string): Promise { + if (!await fs.pathExists(this.packageFile)) { + await this.init(); + } + + const pkgNoVersion = pkg.split('@')[0]; + if (this.installed.indexOf(pkgNoVersion) !== -1) { + return; + } + + const { code, stderr, stdout } = await spawnProcess('npm', ['install', pkg], this.environment); + if (code > 0) { + throw new Error(stderr.join('\n')); + } + + await this.loadPackageFile(); + } + + public async uninstallPackage(pkg: string): Promise { + if (!await fs.pathExists(this.packageFile)) { + await this.init(); + } + + const { code, stderr, stdout } = await spawnProcess('npm', ['remove', pkg], this.environment); + if (code > 0) { + throw new Error(stderr.join('\n')); + } + + await this.loadPackageFile(); + } +} diff --git a/src/npm/index.ts b/src/npm/index.ts new file mode 100644 index 0000000..6926172 --- /dev/null +++ b/src/npm/index.ts @@ -0,0 +1 @@ +export { NPMExecutor } from './executor'; diff --git a/src/plugin/config.ts b/src/plugin/config.ts new file mode 100644 index 0000000..03833e2 --- /dev/null +++ b/src/plugin/config.ts @@ -0,0 +1,19 @@ +import { IEnvironment } from '../types/environment'; +import { PluginConfiguration } from '../types/plugin-config'; +import { IPluginManifest } from './plugin'; + +export class PluginConfigurator { + private configs: Map = new Map(); + + constructor(private env: IEnvironment) {} + + public async loadConfig(mf: IPluginManifest): Promise { + if (!this.configs.has(mf.name)) { + const conf = new PluginConfiguration(this.env, mf.name); + this.configs.set(mf.name, conf); + return conf; + } + + return this.configs.get(mf.name) as PluginConfiguration; + } +} diff --git a/src/plugin/decorators/auto.ts b/src/plugin/decorators/auto.ts new file mode 100644 index 0000000..67b6c0d --- /dev/null +++ b/src/plugin/decorators/auto.ts @@ -0,0 +1,11 @@ + +export function Auto(): (...args: any[]) => void { + return ( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor, + ) => { + descriptor.value.prototype.__autoexec = 1; + return descriptor; + }; +} diff --git a/src/plugin/decorators/dependency.ts b/src/plugin/decorators/dependency.ts new file mode 100644 index 0000000..35e355e --- /dev/null +++ b/src/plugin/decorators/dependency.ts @@ -0,0 +1,50 @@ + +export function DependencyLoad(dep: string): (...args: any[]) => void { + return ( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor, + ) => { + const originalMethod = descriptor.value; + descriptor.value = function(...args: any[]): void { + const self = this as any; + self.stream.on(self.name, 'pluginLoaded', (plugin: any) => { + if (typeof plugin !== 'string') { + plugin = plugin.metadata.name; + } + if (plugin !== dep) { + return; + } + originalMethod.apply(self, plugin); + }); + }; + // Set the function to be autoexecuted when the plugin is initialized. + descriptor.value.prototype.__autoexec = 1; + return descriptor; + }; +} + +export function DependencyUnload(dep: string): (...args: any[]) => void { + return ( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor, + ) => { + const originalMethod = descriptor.value; + descriptor.value = function(...args: any[]): void { + const self = this as any; + self.stream.on(self.name, 'pluginUnloaded', (plugin: any) => { + if (typeof plugin !== 'string') { + plugin = plugin.metadata.name; + } + if (plugin !== dep) { + return; + } + originalMethod.apply(self, plugin); + }); + }; + // Set the function to be autoexecuted when the plugin is initialized. + descriptor.value.prototype.__autoexec = 1; + return descriptor; + }; +} diff --git a/src/plugin/decorators/eventlistener.ts b/src/plugin/decorators/eventlistener.ts new file mode 100644 index 0000000..ef0ccb7 --- /dev/null +++ b/src/plugin/decorators/eventlistener.ts @@ -0,0 +1,21 @@ + +export function EventListener(event: string): (...args: any[]) => void { + return ( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor, + ) => { + const originalMethod = descriptor.value; + descriptor.value = function(...args: any[]): void { + // This ugly hack because TS thinks 'this' + // is still referring to 'PropertyDescriptor' + const self = this as any; + self.stream.on(self.name, event, (...data: any[]) => + originalMethod.apply(self, data), + ); + }; + // Set the function to be autoexecuted when the plugin is initialized. + descriptor.value.prototype.__autoexec = 1; + return descriptor; + }; +} diff --git a/src/plugin/decorators/index.ts b/src/plugin/decorators/index.ts new file mode 100644 index 0000000..95d4954 --- /dev/null +++ b/src/plugin/decorators/index.ts @@ -0,0 +1,3 @@ +export { EventListener } from './eventlistener'; +export { Auto } from './auto'; +export { DependencyLoad, DependencyUnload } from './dependency'; diff --git a/src/plugin/index.ts b/src/plugin/index.ts new file mode 100644 index 0000000..6eb5077 --- /dev/null +++ b/src/plugin/index.ts @@ -0,0 +1,5 @@ +export { PluginConfigurator } from './config'; +export { PluginMetaLoader } from './loader'; +export { PluginManager } from './manager'; +export { IPlugin, IPluginManifest, Plugin } from './plugin'; +export * from './decorators'; diff --git a/src/plugin/loader.ts b/src/plugin/loader.ts new file mode 100644 index 0000000..6c3fe0d --- /dev/null +++ b/src/plugin/loader.ts @@ -0,0 +1,74 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; + +import { IEnvironment } from '../types/environment'; +import { IPluginManifest } from './plugin'; + +export class PluginMetaLoader { + constructor(private env: IEnvironment) {} + + public async load(name: string): Promise { + if (name === 'squeebot') { + throw new Error('Illegal name.'); + } + + const fullpath = path.join(this.env.pluginsPath, name); + const metapath = path.join(fullpath, 'plugin.json'); + if (!await fs.pathExists(metapath)) { + throw new Error('Not a plugin directory.'); + } + + // Read the metadata + const json = await fs.readJson(metapath); + + // Mandatory fields + if (!json.name) { + throw new Error('Plugin metadata does not specify a name, for some reason'); + } + + if (json.name === 'squeebot') { + throw new Error('Illegal name.'); + } + + if (!json.version) { + throw new Error('Plugin metadata does not specify a version, for some reason'); + } + + // Ensure main file exists + if (!json.main) { + json.main = 'plugin.js'; + } + + const mainfile = path.resolve(fullpath, json.main); + if (!await fs.pathExists(mainfile)) { + throw new Error('Plugin does not have a main file or it is misconfigured.'); + } + + json.fullPath = fullpath; + json.main = mainfile; + + if (!json.dependencies) { + json.dependencies = []; + } + + if (!json.npmDependencies) { + json.npmDependencies = []; + } + + return json; + } + + public async loadAll(): Promise { + const dirlist = await fs.readdir(this.env.pluginsPath); + const plugins: IPluginManifest[] = []; + for (const file of dirlist) { + try { + const plugin = await this.load(path.basename(file)); + plugins.push(plugin); + } catch (e) { + console.error(e); + } + } + return plugins; + } +} diff --git a/src/plugin/manager.ts b/src/plugin/manager.ts new file mode 100644 index 0000000..a088a37 --- /dev/null +++ b/src/plugin/manager.ts @@ -0,0 +1,264 @@ +import * as path from 'path'; +import { IEnvironment } from '../types/environment'; +import { IPlugin, IPluginManifest, Plugin } from './plugin'; + +import { PluginConfiguration } from '../types/plugin-config'; +import { PluginConfigurator } from './config'; + +import { ScopedEventEmitter } from '../util/events'; + +import { NPMExecutor } from '../npm/executor'; + +import { logger } from '../core/logger'; + +export function requireNoCache(file: string): object | null { + const fullPath = path.resolve(file); + const mod = require(fullPath); + if (require.cache && require.cache[fullPath]) { + delete require.cache[fullPath]; + } + return mod; +} + +export class PluginManager { + private plugins: Map = new Map(); + private configs: PluginConfigurator = new PluginConfigurator(this.environment); + + constructor( + private availablePlugins: IPluginManifest[], + private stream: ScopedEventEmitter, + private environment: IEnvironment, + private npm: NPMExecutor) { + this.addEvents(); + } + + public getAvailableByName(name: string): IPluginManifest | null { + for (const pl of this.availablePlugins) { + if (pl.name === name) { + return pl; + } + } + return null; + } + + public getLoadedByName(name: string): IPlugin | null { + if (this.plugins.has(name)) { + return this.plugins.get(name) as IPlugin; + } + return null; + } + + public addAvailable(manifest: IPluginManifest | IPluginManifest[]): boolean { + // Automatically add arrays of manifests + if (Array.isArray(manifest)) { + let returnValue = true; + for (const mf of manifest) { + if (!this.addAvailable(mf)) { + returnValue = false; + } + } + return returnValue; + } + + if (this.getAvailableByName(manifest.name)) { + return false; + } + + this.availablePlugins.push(manifest); + return true; + } + + public removeAvailable(plugin: IPluginManifest | IPluginManifest[] | string | string[]): boolean { + let returnValue = false; + if (Array.isArray(plugin)) { + returnValue = true; + for (const mf of plugin) { + if (!this.removeAvailable(mf)) { + returnValue = false; + } + } + return returnValue; + } + + // By name + if (typeof plugin === 'string') { + const p = this.getAvailableByName(plugin); + if (!p) { + return false; + } + plugin = p; + } + + const result: IPluginManifest[] = []; + for (const mf of this.availablePlugins) { + if (mf === plugin) { + returnValue = true; + this.stream.emit('pluginUnload', mf); + continue; + } + result.push(mf); + } + this.availablePlugins = result; + + return returnValue; + } + + public removeRepository(repo: string): boolean { + const list: IPluginManifest[] = []; + for (const mf of this.availablePlugins) { + if (mf.repository && mf.repository === repo) { + list.push(mf); + } + } + return this.removeAvailable(list); + } + + public async load(plugin: IPluginManifest): Promise { + // Check dependencies + const requires = []; + logger.debug('Loading plugin', plugin.name); + for (const dep of plugin.dependencies) { + if (dep === plugin.name) { + throw new Error(`Plugin "${plugin.name}" cannot depend on itself.`); + } + + const existing = this.getLoadedByName(dep); + if (!existing) { + const available = this.getAvailableByName(dep); + if (!available) { + throw new Error(`Plugin dependency "${dep}" resolution failed for "${plugin.name}"`); + } + requires.push(available); + } + } + + // Load dependencies + logger.debug('Loading plugin %s dependencies', plugin.name); + for (const manifest of requires) { + try { + await this.load(manifest); + } catch (e) { + throw new Error(`Plugin dependency "${manifest.name}" loading failed for "${plugin.name}": ${e.stack}`); + } + } + + // Load npm modules + logger.debug('Loading plugin %s npm modules', plugin.name); + for (const depm of plugin.npmDependencies) { + try { + await this.npm.installPackage(depm); + } catch (e) { + throw new Error(`Plugin dependency "${depm}" installation failed for "${plugin.name}": ${e.stack}`); + } + } + + // Load the configuration + const config: PluginConfiguration = await this.configs.loadConfig(plugin); + + // Load the module + logger.debug('Loading plugin %s module', plugin.name); + const PluginModule = requireNoCache(path.resolve(plugin.fullPath, plugin.main)) as any; + if (!PluginModule) { + throw new Error(`Plugin "${plugin.name}" loading failed.`); + } + + // Construct an instance of the module + logger.debug('Instancing plugin %s', plugin.name); + const loaded = new PluginModule(plugin, this.stream, config); + try { + // Call the initializer + if (loaded.initialize) { + loaded.initialize.call(loaded); + } + + // Call methods that are supposed to be executed automatically on load. + // This is really nasty and probably shouldn't be done, but I don't care! + for (const name of Object.getOwnPropertyNames(PluginModule.prototype)) { + // Prevent double initialization + if (name === 'initialize') { + continue; + } + + if (PluginModule.prototype[name] && + PluginModule.prototype[name].prototype && + PluginModule.prototype[name].prototype.__autoexec) { + loaded[name].call(loaded); + } + } + } catch (e) { + throw new Error(`Plugin "${plugin.name}" initialization failed: ${e.stack}`); + } + + this.plugins.set(plugin.name, loaded); + this.stream.emit('pluginLoaded', loaded); + + // Inform the new plugin that it's dependencies are available + for (const depn of plugin.dependencies) { + this.stream.emitTo(plugin.name, 'pluginLoaded', this.plugins.get(depn)); + } + + return loaded; + } + + private addEvents(): void { + this.stream.on('core', 'pluginLoad', (mf: IPluginManifest | string) => { + if (typeof mf === 'string') { + const manifest = this.getAvailableByName(mf); + if (manifest) { + return this.load(manifest).catch((e) => logger.error(e.stack)); + } + } + }); + + this.stream.on('core', 'pluginUnloaded', (mf: IPlugin | string) => { + if (typeof mf !== 'string') { + mf = mf.manifest.name; + } + + // Delete plugin from the list of loaded plugins + this.plugins.delete(mf); + + // Remove all listeners created by the plugin + this.stream.removeName(mf); + }); + + this.stream.on('core', 'pluginKill', (mf: IPlugin | string) => { + const pluginName = (typeof mf === 'string' ? mf : mf.manifest.name); + logger.debug('Killing plugin %s', pluginName); + + this.stream.emitTo(pluginName, 'pluginUnload', pluginName); + this.stream.emit('pluginUnloaded', mf); + }); + + this.stream.on('core', 'shutdown', (state: number) => { + // When the shutdown is initiated, this state will be zero. + // We will be re-emitting this event with a higher state when + // all the plugins have finished shutting down. + if (state !== 0) { + return; + } + + logger.debug('Shutdown has been received by plugin manager'); + + // Shutting down all the plugins + for (const [name, plugin] of this.plugins) { + this.stream.emitTo(name, 'pluginUnload', name); + } + + // Every second check for plugins + let testCount = 0; + const testInterval = setInterval(() => { + // There's still plugins loaded.. + if (this.plugins.size > 0 && testCount < 5) { + testCount++; + return; + } + + // Shut down when there are no more plugins active or + // after 5 seconds we just force shutdown + clearInterval(testInterval); + this.stream.emitTo('core', 'shutdown', 1); + }, 1000); + }); + } +} diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts new file mode 100644 index 0000000..998b877 --- /dev/null +++ b/src/plugin/plugin.ts @@ -0,0 +1,49 @@ +import { logger } from '../core/logger'; +import { PluginConfiguration } from '../types/plugin-config'; +import { ScopedEventEmitter } from '../util/events'; + +export interface IPlugin { + manifest: IPluginManifest; + stream: ScopedEventEmitter; + config: PluginConfiguration; +} + +export class Plugin implements IPlugin { + constructor( + public manifest: IPluginManifest, + public stream: ScopedEventEmitter, + public config: PluginConfiguration) {} + + public initialize(): void {} + + private get name(): string { + return this.manifest.name; + } + + private get version(): string { + return this.manifest.version; + } + + private addEventListener(name: string, fn: any): void { + this.stream.on(this.name, name, fn); + } + + private emit(event: string, fn: any): void { + this.stream.emit.call(this.stream, event, fn); + } + + private emitTo(name: string, event: string, fn: any): void { + this.stream.emitTo.call(this.stream, name, event, fn); + } +} + +export interface IPluginManifest { + repository: string; + fullPath: string; + main: string; + name: string; + version: string; + description: string; + dependencies: string[]; + npmDependencies: string[]; +} diff --git a/src/types/config.ts b/src/types/config.ts new file mode 100644 index 0000000..2baa4bb --- /dev/null +++ b/src/types/config.ts @@ -0,0 +1,50 @@ +import * as fs from 'fs-extra'; + +import { IEnvironment } from './environment'; + +export class Configuration { + private config: any = {}; + private dirty = false; + + constructor(private env: IEnvironment, private file: string, private defaults?: any) {} + + public async load(): Promise { + if (!await fs.pathExists(this.file)) { + this.saveDefaults(); + return; + } + + const json = await fs.readJson(this.file); + this.config = json; + + if (this.defaults) { + this.config = Object.assign({}, this.defaults, json); + } + } + + public async save(force = false): Promise { + if (force) { + return this.write(); + } + + this.dirty = true; + } + + public get(key: string, defval?: any): any { + if (!this.config[key]) { + return defval; + } + return this.config[key]; + } + + private saveDefaults(): void { + this.config = this.defaults || {}; + this.save(true); + } + + private async write(): Promise { + return fs.writeJson(this.file, this.config); + } + + private writeTask(): void {} +} diff --git a/src/types/environment.ts b/src/types/environment.ts new file mode 100644 index 0000000..7742656 --- /dev/null +++ b/src/types/environment.ts @@ -0,0 +1,8 @@ + +export interface IEnvironment { + path: string; + pluginsPath: string; + repositoryPath: string; + configurationPath: string; + environment: string; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..5c8f0bc --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,4 @@ +export { Configuration } from './config'; +export { IEnvironment } from './environment'; +export { PluginConfiguration } from './plugin-config'; +export { IMessage } from './message'; diff --git a/src/types/message.ts b/src/types/message.ts new file mode 100644 index 0000000..5b29795 --- /dev/null +++ b/src/types/message.ts @@ -0,0 +1,8 @@ +import { IPlugin } from '../plugin/plugin'; + +export interface IMessage { + data: any; + source: IPlugin; + time: Date; + resolve(...args: any[]): void; +} diff --git a/src/types/plugin-config.ts b/src/types/plugin-config.ts new file mode 100644 index 0000000..1174344 --- /dev/null +++ b/src/types/plugin-config.ts @@ -0,0 +1,10 @@ +import * as path from 'path'; + +import { Configuration } from './config'; +import { IEnvironment } from './environment'; + +export class PluginConfiguration extends Configuration { + constructor(env: IEnvironment, name: string) { + super(env, path.join(env.configurationPath, name + '.json')); + } +} diff --git a/src/util/events.ts b/src/util/events.ts new file mode 100644 index 0000000..bb88bb0 --- /dev/null +++ b/src/util/events.ts @@ -0,0 +1,73 @@ +export class ScopedEventEmitter { + private listeners: {[key: string]: any[]}; + public addEventListener = this.on; + + constructor() { + this.listeners = {}; + } + + public on(name: string, event: string, func: any, once = false): void { + if (!func || !event || !name) { + throw new Error('missing arguments'); + } + + if (!this.listeners[name]) { + this.listeners[name] = []; + } + + this.listeners[name].push({ + event, func, name, once, + }); + } + + public once(name: string, event: string, func: any): void { + this.on(name, event, func, true); + } + + public emit(event: string, ...args: any[]): void { + for (const name in this.listeners) { + for (const i in this.listeners[name]) { + if (!this.listeners[name]) { + break; + } + const listener = this.listeners[name][i]; + + if (listener.event === event && listener.func) { + listener.func(...args); + + if (listener.once) { + this.listeners[name].splice(parseInt(i, 10), 1); + } + } + } + } + } + + public emitTo(name: string, event: string, ...args: any[]): void { + if (!this.listeners[name]) { + return; + } + + for (const i in this.listeners[name]) { + if (!this.listeners[name]) { + break; + } + + const listener = this.listeners[name][i]; + + if (listener.event === event && listener.func) { + listener.func(...args); + + if (listener.once) { + this.listeners[name].splice(parseInt(i, 10), 1); + } + } + } + } + + public removeName(name: string): void { + if (this.listeners[name]) { + delete this.listeners[name]; + } + } +} diff --git a/src/util/index.ts b/src/util/index.ts new file mode 100644 index 0000000..75c1bec --- /dev/null +++ b/src/util/index.ts @@ -0,0 +1,2 @@ +export { ScopedEventEmitter } from './events'; +export { IProcessData, spawnProcess, execProcess } from './run'; diff --git a/src/util/run.ts b/src/util/run.ts new file mode 100644 index 0000000..a16a273 --- /dev/null +++ b/src/util/run.ts @@ -0,0 +1,37 @@ +import { exec, spawn } from 'child_process'; +import { IEnvironment } from '../types/environment'; + +export interface IProcessData { + code: number; + stderr: string[]; + stdout: string[]; +} + +export async function spawnProcess(execp: string, args: any[], env: IEnvironment): Promise { + return new Promise((resolve, reject): void => { + const process = spawn(execp, args, { cwd: env.path }); + const stdout: string[] = []; + const stderr: string[] = []; + process.stdout.on('data', (d: any) => { + stdout.push(d.toString()); + }); + + process.stderr.on('data', (d: any) => { + stderr.push(d.toString()); + }); + + process.on('exit', (code: number) => { + resolve({ + code, stderr, stdout, + }); + }); + }); +} + +export async function execProcess(execp: string, env: IEnvironment): Promise { + return new Promise((resolve, reject): void => { + const cprog = exec(execp, (error: any, stdout: any, stderr: any): void => resolve({ + code: error.code, stderr, stdout, + })); + }); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..309ae67 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,68 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": false, /* Generates corresponding '.map' file. */ + "outDir": "lib", /* Redirect output structure to the directory. */ + "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..bc10d1c --- /dev/null +++ b/tslint.json @@ -0,0 +1,150 @@ +{ + "extends": "tslint:recommended", + "rules": { + "align": { + "options": [ + "parameters", + "statements" + ] + }, + "array-type": false, + "arrow-return-shorthand": true, + "curly": true, + "deprecation": { + "severity": "warning" + }, + "eofline": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": { + "options": [ + "spaces" + ] + }, + "max-classes-per-file": false, + "max-line-length": [ + true, + 140 + ], + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-empty": false, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-var-requires": false, + "object-literal-key-quotes": [ + true, + "as-needed" + ], + "quotemark": [ + true, + "single" + ], + "semicolon": { + "options": [ + "always" + ] + }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" + } + }, + "typedef": [ + true, + "call-signature" + ], + "forin": false, + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-pascal-case" + ] + }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + }, + "component-class-suffix": true, + "contextual-lifecycle": true, + "directive-class-suffix": true, + "no-conflicting-lifecycle": true, + "no-host-metadata-property": true, + "no-input-rename": true, + "no-inputs-metadata-property": true, + "no-output-native": true, + "no-output-on-prefix": true, + "no-output-rename": true, + "no-outputs-metadata-property": true, + "template-banana-in-box": true, + "template-no-negated-async": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true, + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +}