hello world, squeebot 3

This commit is contained in:
Evert Prants 2020-11-21 17:41:08 +02:00
commit 3a6f5f2d8e
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
29 changed files with 1538 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/dist/
/lib/
/node_modules/
*.tsbuildinfo

404
package-lock.json generated Normal file
View File

@ -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
}
}
}

30
package.json Normal file
View File

@ -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 <evert@lunasqu.ee>",
"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"
}
}

13
src/channel/index.ts Normal file
View File

@ -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
});
}
}
}

42
src/core/environment.ts Normal file
View File

@ -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<IEnvironment> {
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;
}

2
src/core/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { loadEnvironment } from './environment';
export { Logger, logger } from './logger';

67
src/core/logger.ts Normal file
View File

@ -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 };

0
src/index.ts Normal file
View File

69
src/npm/executor.ts Normal file
View File

@ -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<void> {
// 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<void> {
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<void> {
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<void> {
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();
}
}

1
src/npm/index.ts Normal file
View File

@ -0,0 +1 @@
export { NPMExecutor } from './executor';

19
src/plugin/config.ts Normal file
View File

@ -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<string, PluginConfiguration> = new Map();
constructor(private env: IEnvironment) {}
public async loadConfig(mf: IPluginManifest): Promise<PluginConfiguration> {
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;
}
}

View File

@ -0,0 +1,11 @@
export function Auto(): (...args: any[]) => void {
return (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) => {
descriptor.value.prototype.__autoexec = 1;
return descriptor;
};
}

View File

@ -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;
};
}

View File

@ -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;
};
}

View File

@ -0,0 +1,3 @@
export { EventListener } from './eventlistener';
export { Auto } from './auto';
export { DependencyLoad, DependencyUnload } from './dependency';

5
src/plugin/index.ts Normal file
View File

@ -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';

74
src/plugin/loader.ts Normal file
View File

@ -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<IPluginManifest> {
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<IPluginManifest[]> {
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;
}
}

264
src/plugin/manager.ts Normal file
View File

@ -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<string, IPlugin> = 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<IPlugin> {
// 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);
});
}
}

49
src/plugin/plugin.ts Normal file
View File

@ -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[];
}

50
src/types/config.ts Normal file
View File

@ -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<void> {
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<void> {
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<void> {
return fs.writeJson(this.file, this.config);
}
private writeTask(): void {}
}

8
src/types/environment.ts Normal file
View File

@ -0,0 +1,8 @@
export interface IEnvironment {
path: string;
pluginsPath: string;
repositoryPath: string;
configurationPath: string;
environment: string;
}

4
src/types/index.ts Normal file
View File

@ -0,0 +1,4 @@
export { Configuration } from './config';
export { IEnvironment } from './environment';
export { PluginConfiguration } from './plugin-config';
export { IMessage } from './message';

8
src/types/message.ts Normal file
View File

@ -0,0 +1,8 @@
import { IPlugin } from '../plugin/plugin';
export interface IMessage {
data: any;
source: IPlugin;
time: Date;
resolve(...args: any[]): void;
}

View File

@ -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'));
}
}

73
src/util/events.ts Normal file
View File

@ -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];
}
}
}

2
src/util/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { ScopedEventEmitter } from './events';
export { IProcessData, spawnProcess, execProcess } from './run';

37
src/util/run.ts Normal file
View File

@ -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<IProcessData> {
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<IProcessData> {
return new Promise((resolve, reject): void => {
const cprog = exec(execp, (error: any, stdout: any, stderr: any): void => resolve({
code: error.code, stderr, stdout,
}));
});
}

68
tsconfig.json Normal file
View File

@ -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. */
}
}

150
tslint.json Normal file
View File

@ -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"
]
}
}