eslint, require-no-cache now deletes all requires from plugin directory from cache

This commit is contained in:
Evert Prants 2021-12-18 10:32:30 +02:00
parent f2af1891ee
commit 81b8e7581a
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
21 changed files with 2747 additions and 361 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
*.js
*.d.ts

46
.eslintrc.js Normal file
View File

@ -0,0 +1,46 @@
module.exports = {
'env': {
'es2021': true,
'node': true
},
'extends': [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
'parser': '@typescript-eslint/parser',
'parserOptions': {
'ecmaVersion': 13,
'sourceType': 'module',
'project': 'tsconfig.json',
'tsconfigRootDir': __dirname,
},
'plugins': [
'@typescript-eslint'
],
'rules': {
'no-empty': [
'error',
{
'allowEmptyCatch': true,
}
],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'indent': [
'error',
2
],
'linebreak-style': [
'error',
'unix'
],
'quotes': [
'error',
'single'
],
'semi': [
'error',
'always'
]
}
};

2776
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@squeebot/core", "name": "@squeebot/core",
"version": "3.3.5", "version": "3.3.6",
"description": "Squeebot v3 core for the execution environment", "description": "Squeebot v3 core for the execution environment",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -23,7 +23,8 @@
"@types/node": "^16.7.10", "@types/node": "^16.7.10",
"@types/semver": "^7.3.8", "@types/semver": "^7.3.8",
"@types/tar": "^4.0.5", "@types/tar": "^4.0.5",
"tslint": "^6.1.3", "@typescript-eslint/eslint-plugin": "^5.7.0",
"eslint": "^8.4.1",
"typescript": "^4.4.2" "typescript": "^4.4.2"
}, },
"dependencies": { "dependencies": {

View File

@ -52,11 +52,11 @@ export function httpGET(
} }
return httpGET(res.headers.location as string, return httpGET(res.headers.location as string,
headers, headers,
restrictToText, restrictToText,
saveTo, saveTo,
lback, lback,
).then(resolve, reject); ).then(resolve, reject);
} }
if (saveTo) { if (saveTo) {

View File

@ -6,10 +6,10 @@
*/ */
export function sanitizeEscapedText(text: string): string { export function sanitizeEscapedText(text: string): string {
return text.replace(/\n/g, ' ') return text.replace(/\n/g, ' ')
.replace(/&/g, '&') .replace(/&/g, '&')
.replace(/&lt;/g, '<') .replace(/&lt;/g, '<')
.replace(/&gt;/g, '>') .replace(/&gt;/g, '>')
.replace(/&quot;/g, '"') .replace(/&quot;/g, '"')
.replace(/&apos;/g, '\'') .replace(/&apos;/g, '\'')
.trim(); .trim();
} }

View File

@ -15,7 +15,7 @@ const dirs: {[key: string]: string} = {
* @param chroot Change bot root to this instead of the path in the environment * @param chroot Change bot root to this instead of the path in the environment
* @returns Squeebot environment * @returns Squeebot environment
*/ */
export async function loadEnvironment(enviroFile: string = 'squeebot.env.json', chroot?: string): Promise<IEnvironment> { export async function loadEnvironment(enviroFile = 'squeebot.env.json', chroot?: string): Promise<IEnvironment> {
if (!await fs.pathExists(enviroFile)) { if (!await fs.pathExists(enviroFile)) {
throw new Error('Environment file does not exist.'); throw new Error('Environment file does not exist.');
} }

View File

@ -22,7 +22,7 @@ export class Logger {
const old = this.console[index]; const old = this.console[index];
this.console[index] = (...data: any[]): void => { this.console[index] = (...data: any[]): void => {
rl.output.write('\x1b[2K\r'); rl.output.write('\x1b[2K\r');
old.apply(null, data); old(...data);
rl.prompt(true); rl.prompt(true);
}; };
} }
@ -42,20 +42,20 @@ export class Logger {
} }
switch (ltype) { switch (ltype) {
case 'info': case 'info':
message.push('[ INFO]'); message.push('[ INFO]');
break; break;
case 'debug': case 'debug':
message.push('[DEBUG]'); message.push('[DEBUG]');
break; break;
case 'warn': case 'warn':
message.push('[ WARN]'); message.push('[ WARN]');
cfunc = this.console[1]; cfunc = this.console[1];
break; break;
case 'error': case 'error':
message.push('[ERROR]'); message.push('[ERROR]');
cfunc = this.console[2]; cfunc = this.console[2];
break; break;
} }
// Short dance to apply formatting // Short dance to apply formatting
@ -65,7 +65,7 @@ export class Logger {
final = util.format(data[0], ...fargs); final = util.format(data[0], ...fargs);
} }
message.push(final); message.push(final);
cfunc.apply(null, message); cfunc(...message);
} }
/** /**

View File

@ -102,6 +102,6 @@ export class NPMExecutor {
} }
private removeVersionWildcard(str: string): string { private removeVersionWildcard(str: string): string {
return str.replace(/\^|\>|\=/, ''); return str.replace(/\^|>|=/, '');
} }
} }

View File

@ -3,8 +3,8 @@
* Declare this plugin as one that the user can configure. * Declare this plugin as one that the user can configure.
* @param defconf Default configuration * @param defconf Default configuration
*/ */
export function Configurable(defconf: any): Function { export function Configurable(defconf: any): (...arg: any[]) => any {
return (constructor: Function): void => { return (constructor: (...arg: any[]) => any): void => {
constructor.prototype.__defconf = defconf; constructor.prototype.__defconf = defconf;
}; };
} }

View File

@ -3,8 +3,8 @@
* Declare this plugin as one that utilises services. * Declare this plugin as one that utilises services.
* @param servtype Service class, usually a Protocol * @param servtype Service class, usually a Protocol
*/ */
export function InjectService(servtype: any): Function { export function InjectService(servtype: any): (...arg: any[]) => any {
return (constructor: Function): void => { return (constructor: (...arg: any[]) => any): void => {
constructor.prototype.__service = servtype; constructor.prototype.__service = servtype;
}; };
} }

View File

@ -36,7 +36,7 @@ export class PluginMetaLoader {
throw new Error('Plugin metadata does not specify a name, for some reason'); throw new Error('Plugin metadata does not specify a name, for some reason');
} }
if (json.name === 'squeebot' || !(/^[a-zA-Z0-9_\-]+$/.test(json.name))) { if (json.name === 'squeebot' || !(/^[a-zA-Z0-9_-]+$/.test(json.name))) {
throw new Error('Illegal name.'); throw new Error('Illegal name.');
} }
@ -76,12 +76,13 @@ export class PluginMetaLoader {
public async loadAll(ignoreErrors = false): Promise<IPluginManifest[]> { public async loadAll(ignoreErrors = false): Promise<IPluginManifest[]> {
const dirlist = await fs.readdir(this.env.pluginsPath); const dirlist = await fs.readdir(this.env.pluginsPath);
const plugins: IPluginManifest[] = []; const plugins: IPluginManifest[] = [];
for (const file of dirlist) { for (const file of dirlist) {
// Ignore hidden files and non-directories // Ignore hidden files and non-directories
const fpath = path.join(this.env.pluginsPath, file); const fpath = path.join(this.env.pluginsPath, file);
if (file.indexOf('.') === 0 || const isDirectory = (await fs.lstat(fpath)).isDirectory();
file === 'node_modules' ||
!(await fs.lstat(fpath)).isDirectory()) { if (!isDirectory || file.startsWith('.') || file === 'node_modules') {
continue; continue;
} }
@ -89,10 +90,9 @@ export class PluginMetaLoader {
const plugin = await this.load(file); const plugin = await this.load(file);
plugins.push(plugin); plugins.push(plugin);
} catch (e) { } catch (e) {
if (ignoreErrors) { if (!ignoreErrors) {
continue; logger.error(e);
} }
logger.error(e);
} }
} }
return plugins; return plugins;

View File

@ -84,7 +84,7 @@ export class PluginManager {
return true; return true;
} }
if (!(/^[a-zA-Z0-9_\-]+$/.test(manifest.name))) { if (!(/^[a-zA-Z0-9_-]+$/.test(manifest.name))) {
throw new Error('Illegal name for a plugin!'); throw new Error('Illegal name for a plugin!');
} }
@ -284,8 +284,9 @@ export class PluginManager {
/** /**
* Restart a loaded plugin. * Restart a loaded plugin.
* @param mf Plugin instance, plugin manifest or plugin name * @param mf Plugin instance, plugin manifest or plugin name
* @param wait Waits for the plugin to be available before resolving
*/ */
public async restart(mf: IPluginManifest | IPlugin | string): Promise<void> { public async restart(mf: IPluginManifest | IPlugin | string, wait = false): Promise<void> {
let manifest; let manifest;
if (typeof mf === 'string') { if (typeof mf === 'string') {
manifest = this.getAvailableByName(mf); manifest = this.getAvailableByName(mf);
@ -299,13 +300,34 @@ export class PluginManager {
throw new Error('Plugin not found'); throw new Error('Plugin not found');
} }
if (!this.getLoadedByName(manifest.name)) { const pluginName = manifest.name;
if (!this.getLoadedByName(pluginName)) {
this.load(manifest); this.load(manifest);
return; return;
} }
this.restartQueue.set(manifest.name, manifest); this.restartQueue.set(pluginName, manifest);
this.stream.emitTo(manifest.name, 'pluginUnload', manifest.name); this.stream.emitTo(pluginName, 'pluginUnload', pluginName);
if (wait) {
return new Promise((resolve, reject) => {
let retries = 0;
const checkInterval = setInterval(() => {
// Plugin has been loaded, resolve the promise
if (this.getLoadedByName(pluginName)) {
clearInterval(checkInterval);
return resolve();
}
// Increment retry count and wait for next iteration
retries++;
if (retries >= 20) {
// Give up after 10 seconds
clearInterval(checkInterval);
reject(new Error('Could not determine loaded status within a reasonable time frame.'));
}
}, 500);
});
}
} }
/** /**

View File

@ -24,6 +24,7 @@ export class Plugin implements IPlugin {
* Called when plugin first starts. * Called when plugin first starts.
* Please use this instead of the constructor. * Please use this instead of the constructor.
*/ */
// eslint-disable-next-line @typescript-eslint/no-empty-function
public initialize(): void {} public initialize(): void {}
public get name(): string { public get name(): string {

View File

@ -163,7 +163,7 @@ export class RepositoryManager {
throw new Error('Invalid metadata file for repository.'); throw new Error('Invalid metadata file for repository.');
} }
if (!(/^[a-zA-Z0-9_\-\+]+$/.test(meta.name))) { if (!(/^[a-zA-Z0-9_\-+]+$/.test(meta.name))) {
throw new Error('Illegal name for repository!'); throw new Error('Illegal name for repository!');
} }
@ -286,7 +286,7 @@ export class RepositoryManager {
throw new Error('Invalid repository file ' + rf); throw new Error('Invalid repository file ' + rf);
} }
if (!(/^[a-zA-Z0-9_\-\+]+$/.test(contents.name))) { if (!(/^[a-zA-Z0-9_\-+]+$/.test(contents.name))) {
throw new Error(`"${rf}" is an illegal name for a repository!`); throw new Error(`"${rf}" is an illegal name for a repository!`);
} }

View File

@ -28,8 +28,12 @@ export class Configuration {
const json = await fs.readJson(this.file); const json = await fs.readJson(this.file);
this.config = json; this.config = json;
// Assign default values to existing configuration and resave.
// Eliminates the need for plugins to overwrite the configuration file
// when exiting.
if (this.defaults) { if (this.defaults) {
this.config = Object.assign({}, this.defaults, json); this.config = Object.assign({}, this.defaults, json);
await this.save();
} }
} }

View File

@ -184,18 +184,18 @@ export class Formatter {
// Special types // Special types
if (elemParams && elemParams.type) { if (elemParams && elemParams.type) {
switch (elemParams.type) { switch (elemParams.type) {
case 'time': case 'time':
elemValue = new Date(elemValue).toString(); elemValue = new Date(elemValue).toString();
break; break;
case 'metric': case 'metric':
elemValue = thousandsSeparator(elemValue); elemValue = thousandsSeparator(elemValue);
break; break;
case 'timesince': case 'timesince':
elemValue = timeSince(elemValue); elemValue = timeSince(elemValue);
break; break;
case 'duration': case 'duration':
elemValue = toHHMMSS(elemValue); elemValue = toHHMMSS(elemValue);
break; break;
} }
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { EMessageType, IMessage } from './message'; import { EMessageType, IMessage } from './message';
import { Protocol } from './protocol'; import { Protocol } from './protocol';

View File

@ -11,7 +11,7 @@ export interface IMessageData {
value: any; value: any;
} }
export type IMessageHandler = (data: IMessageData | string, reject: Function, next: IMessageHandler) => void; export type IMessageHandler = (data: IMessageData | string, reject: (error: Error) => void, next: IMessageHandler) => void;
/** /**
* Staged messaging handler * Staged messaging handler

View File

@ -10,9 +10,18 @@ export { IProcessData, spawnProcess, execProcess } from './run';
*/ */
export function requireNoCache(file: string): object | undefined { export function requireNoCache(file: string): object | undefined {
const fullPath = path.resolve(file); const fullPath = path.resolve(file);
// TODO: maybe use new "import" function?
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mod = require(fullPath); const mod = require(fullPath);
if (require.cache && require.cache[fullPath]) { if (require.cache && require.cache[fullPath]) {
delete require.cache[fullPath]; delete require.cache[fullPath];
// Delete in-plugin-folder requires from cache as well
const dirname = path.dirname(fullPath);
const cacheEntries = Object.keys(require.cache).filter((entry) => entry.startsWith(dirname));
cacheEntries.forEach((entry) => {
delete require.cache[entry];
});
} }
return mod; return mod;
} }

View File

@ -1,122 +0,0 @@
{
"extends": "tslint:recommended",
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
"deprecation": {
"severity": "warning"
},
"eofline": true,
"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,
"ban-types": {
"function": 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"
]
}
}
}