hello world, squeebot 3
This commit is contained in:
commit
3a6f5f2d8e
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/dist/
|
||||
/lib/
|
||||
/node_modules/
|
||||
*.tsbuildinfo
|
404
package-lock.json
generated
Normal file
404
package-lock.json
generated
Normal 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
30
package.json
Normal 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
13
src/channel/index.ts
Normal 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
42
src/core/environment.ts
Normal 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
2
src/core/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { loadEnvironment } from './environment';
|
||||
export { Logger, logger } from './logger';
|
67
src/core/logger.ts
Normal file
67
src/core/logger.ts
Normal 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
0
src/index.ts
Normal file
69
src/npm/executor.ts
Normal file
69
src/npm/executor.ts
Normal 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
1
src/npm/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { NPMExecutor } from './executor';
|
19
src/plugin/config.ts
Normal file
19
src/plugin/config.ts
Normal 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;
|
||||
}
|
||||
}
|
11
src/plugin/decorators/auto.ts
Normal file
11
src/plugin/decorators/auto.ts
Normal 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;
|
||||
};
|
||||
}
|
50
src/plugin/decorators/dependency.ts
Normal file
50
src/plugin/decorators/dependency.ts
Normal 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;
|
||||
};
|
||||
}
|
21
src/plugin/decorators/eventlistener.ts
Normal file
21
src/plugin/decorators/eventlistener.ts
Normal 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;
|
||||
};
|
||||
}
|
3
src/plugin/decorators/index.ts
Normal file
3
src/plugin/decorators/index.ts
Normal 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
5
src/plugin/index.ts
Normal 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
74
src/plugin/loader.ts
Normal 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
264
src/plugin/manager.ts
Normal 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
49
src/plugin/plugin.ts
Normal 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
50
src/types/config.ts
Normal 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
8
src/types/environment.ts
Normal 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
4
src/types/index.ts
Normal 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
8
src/types/message.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { IPlugin } from '../plugin/plugin';
|
||||
|
||||
export interface IMessage {
|
||||
data: any;
|
||||
source: IPlugin;
|
||||
time: Date;
|
||||
resolve(...args: any[]): void;
|
||||
}
|
10
src/types/plugin-config.ts
Normal file
10
src/types/plugin-config.ts
Normal 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
73
src/util/events.ts
Normal 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
2
src/util/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { ScopedEventEmitter } from './events';
|
||||
export { IProcessData, spawnProcess, execProcess } from './run';
|
37
src/util/run.ts
Normal file
37
src/util/run.ts
Normal 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
68
tsconfig.json
Normal 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
150
tslint.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user