diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..e518c29a --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,253 @@ +"use strict"; +module.exports = { + "env": { + "node": true, + "browser": true, + "commonjs": true, + "es6": true, + }, + "parserOptions": { + "ecmaVersion": 11, + }, + + "overrides": [ + { + "files": ["test/**/*.js", "plugins/*/test/**/*.js"], + "env": { + "mocha": "true", + }, + "rules": { + "prefer-arrow-callback": "off", + }, + }, + ], + + "rules": { + "accessor-pairs": "error", + "array-bracket-newline": "off", + "array-bracket-spacing": ["error", "never"], + "array-callback-return": "error", + "array-element-newline": "off", + "arrow-body-style": "error", + "arrow-parens": "off", + "arrow-spacing": ["error", { "after": true, "before": true }], + "block-scoped-var": "error", + "block-spacing": "error", + "brace-style": ["warn", "1tbs", { "allowSingleLine": true }], + "callback-return": "off", + "camelcase": "off", + "capitalized-comments": "off", + "class-methods-use-this": "off", + "comma-dangle": [ + "error", + { + "arrays": "always-multiline", + "objects": "always-multiline", + "functions": "only-multiline", + }, + ], + "comma-spacing": "warn", + "comma-style": ["error", "last"], + "complexity": "warn", + "computed-property-spacing": ["error", "never"], + "consistent-return": "error", + "consistent-this": "error", + "curly": "warn", + "default-case": "error", + "default-case-last": "error", + "default-param-last": "error", + "dot-location": ["error", "property"], + "dot-notation": ["warn", { "allowKeywords": true }], + "eol-last": "error", + "eqeqeq": "warn", + "func-call-spacing": "error", + "func-name-matching": "error", + "func-names": "off", + "func-style": ["error", "declaration"], + "function-call-argument-newline": "off", + "function-paren-newline": "off", + "generator-star-spacing": ["error", { "before": false, "after": true }], + "global-require": "warn", + "grouped-accessor-pairs": "error", + "guard-for-in": "error", + "handle-callback-err": "error", + "id-blacklist": "error", + "id-length": "off", + "id-match": "error", + "implicit-arrow-linebreak": "error", + "indent": ["error", "tab", { "SwitchCase": 1 }], + "indent-legacy": "off", + "init-declarations": "off", + "jsx-quotes": "error", + "key-spacing": "error", + "keyword-spacing": "warn", + "line-comment-position": "off", + "linebreak-style": ["error", "unix"], + "lines-around-comment": "off", + "lines-around-directive": "off", + "lines-between-class-members": ["error", "always"], + "max-classes-per-file": "off", + "max-depth": "warn", + "max-len": ["warn", { "code": 120 }], + "max-lines": "off", + "max-lines-per-function": "off", + "max-nested-callbacks": "error", + "max-params": "off", + "max-statements": "off", + "max-statements-per-line": "off", + "multiline-comment-style": ["warn", "separate-lines"], + "multiline-ternary": "warn", + "new-cap": "warn", + "new-parens": "error", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": "off", + "no-alert": "error", + "no-array-constructor": "error", + "no-await-in-loop": "off", + "no-bitwise": "off", + "no-buffer-constructor": "error", + "no-caller": "error", + "no-catch-shadow": "error", + "no-confusing-arrow": "error", + "no-console": "off", + "no-constant-condition": ["error", { "checkLoops": false }], + "no-constructor-return": "error", + "no-continue": "off", + "no-div-regex": "error", + "no-duplicate-imports": "error", + "no-else-return": "warn", + "no-empty-function": "off", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "off", + "no-floating-decimal": "error", + "no-implicit-coercion": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-invalid-this": "warn", + "no-iterator": "error", + "no-label-var": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-operators": "warn", + "no-mixed-requires": "error", + "no-multi-assign": "error", + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": "error", + "no-native-reassign": "error", + "no-negated-condition": "off", + "no-negated-in-lhs": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-path-concat": "error", + "no-plusplus": ["warn", { "allowForLoopAfterthoughts": true }], + "no-process-env": "error", + "no-process-exit": "warn", + "no-proto": "error", + "no-restricted-exports": "error", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-modules": "error", + "no-restricted-properties": "error", + "no-restricted-syntax": "error", + "no-return-assign": "error", + "no-return-await": "off", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "warn", + "no-spaced-func": "error", + "no-sync": "error", + "no-tabs": ["error", { "allowIndentationTabs": true }], + "no-template-curly-in-string": "error", + "no-ternary": "off", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef-init": "error", + "no-undefined": "off", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unused-expressions": "error", + "no-use-before-define": "warn", + "no-useless-backreference": "error", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "warn", + "no-void": "error", + "no-warning-comments": "off", + "no-whitespace-before-property": "error", + "nonblock-statement-body-position": "error", + "object-curly-newline": "error", + "object-curly-spacing": "off", + "object-shorthand": "off", + "one-var": "off", + "one-var-declaration-per-line": "error", + "operator-assignment": "off", + "operator-linebreak": "warn", + "padded-blocks": "off", + "padding-line-between-statements": "error", + "prefer-arrow-callback": "warn", + "prefer-const": "off", + "prefer-destructuring": "off", + "prefer-exponentiation-operator": "error", + "prefer-named-capture-group": "off", + "prefer-numeric-literals": "error", + "prefer-object-spread": "error", + "prefer-promise-reject-errors": "error", + "prefer-reflect": "off", + "prefer-regex-literals": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "warn", + "quote-props": "off", + "quotes": ["error", "double", { "avoidEscape": true }], + "radix": ["error", "always"], + "require-atomic-updates": "off", // XXX Not sure why this triggers on process.title = ... + "require-await": "off", + "require-jsdoc": "off", + "require-unicode-regexp": "off", + "rest-spread-spacing": ["error", "never"], + "semi": "error", + "semi-spacing": "error", + "semi-style": "off", + "sort-imports": "error", + "sort-keys": "off", + "sort-vars": "error", + "space-before-blocks": "warn", + "space-before-function-paren": "off", + "space-in-parens": ["error", "never"], + "space-infix-ops": "off", + "space-unary-ops": "error", + "spaced-comment": ["warn", "always"], + "strict": "error", + "switch-colon-spacing": "error", + "symbol-description": "error", + "template-curly-spacing": ["error", "never"], + "template-tag-spacing": "error", + "unicode-bom": ["error", "never"], + "valid-jsdoc": ["error", { "requireReturn": false }], + "vars-on-top": "warn", + "wrap-iife": "error", + "wrap-regex": "off", + "yield-star-spacing": "error", + "yoda": ["error", "never", { "exceptRange": true }], + }, +}; diff --git a/.travis.yml b/.travis.yml index 89adafcb..01d17891 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,3 +11,4 @@ script: - npm run-script ci-cover - cat temp/test/integration/log.txt - codecov -f coverage/lcov.info + - npm run-script lint diff --git a/build-mod.js b/build-mod.js index cf6fa86e..aef57612 100644 --- a/build-mod.js +++ b/build-mod.js @@ -12,11 +12,11 @@ async function main() { const args = yargs .scriptName("build") .options({ - 'clean': { describe: "Remove previous builds", type: 'boolean', default: false }, - 'build': { describe: "Build mod", type: 'boolean', default: true }, - 'pack': { describe: "Pack into zip file", type: 'boolean', default: true }, + "clean": { describe: "Remove previous builds", type: "boolean", default: false }, + "build": { describe: "Build mod", type: "boolean", default: true }, + "pack": { describe: "Pack into zip file", type: "boolean", default: true }, "source-dir": { describe: "Path to mod source files", nargs: 1, type: "string", default: libPath }, - 'output-dir': { describe: "Path to output built mod", nargs: 1, type: 'string', default: "dist" }, + "output-dir": { describe: "Path to output built mod", nargs: 1, type: "string", default: "dist" }, "bump-patch": { describe: "Increment patch number of build", type: "boolean", default: false }, "factorio-version": { describe: "Override factorio_version", type: "string" }, }) @@ -45,7 +45,7 @@ async function build(args) { } if (args.clean) { - let splitter = /^(.*)_(\d+\.\d+\.\d+)(\.zip)?$/ + let splitter = /^(.*)_(\d+\.\d+\.\d+)(\.zip)?$/; for (let entry of await fs.readdir(args.outputDir)) { let match = splitter.exec(entry); if (match) { @@ -66,7 +66,7 @@ async function build(args) { if (args.pack) { let zip = new JSZip(); let walker = klaw(args.sourceDir) - .on('data', item => { + .on("data", item => { if (item.stats.isFile()) { // On Windows the path created uses backslashes as the directory sepparator // but the zip file needs to use forward slashes. We can't use the posix @@ -76,7 +76,7 @@ async function build(args) { } }) ; - await events.once(walker, 'end'); + await events.once(walker, "end"); for (let pathParts of info.additional_files || []) { let filePath = path.join(...pathParts); @@ -89,7 +89,7 @@ async function build(args) { let modPath = path.join(args.outputDir, `${modName}.zip`); console.log(`Writing ${modPath}`); let writeStream = zip.generateNodeStream().pipe(fs.createWriteStream(modPath)); - await events.once(writeStream, 'finish'); + await events.once(writeStream, "finish"); } else { let modDir = path.join(args.outputDir, modName); @@ -111,7 +111,7 @@ async function build(args) { } if (module === require.main) { - main().catch(err => { console.log(err) }); + main().catch(err => { console.log(err); }); } module.exports = { diff --git a/clusterctl.js b/clusterctl.js index 989f9445..b1ae99bd 100644 --- a/clusterctl.js +++ b/clusterctl.js @@ -2,11 +2,12 @@ * Command line interface for controlling a Clusterio cluster * @module */ +"use strict"; const jwt = require("jsonwebtoken"); const fs = require("fs-extra"); const yargs = require("yargs"); const version = require("./package").version; -const asTable = require("as-table").configure({delimiter: ' | '}); +const asTable = require("as-table").configure({ delimiter: " | " }); const chalk = require("chalk"); const events = require("events"); @@ -21,18 +22,20 @@ const config = require("lib/config"); * Formats a parsed Factorio output from lib/factorio into a readable * colorized output using terminal escape codes that can be printed. * + * @param {Object} output - Factorio server output. + * @returns {string} terminal colorized message. * @private */ function formatOutputColored(output) { let time = ""; - if (output.format === 'seconds') { - time = chalk.yellow(output.time.padStart(8)) + ' '; - } else if (output.format === 'date') { - time = chalk.yellow(output.time) + ' '; + if (output.format === "seconds") { + time = chalk.yellow(output.time.padStart(8)) + " "; + } else if (output.format === "date") { + time = chalk.yellow(output.time) + " "; } let info = ""; - if (output.type === 'log') { + if (output.type === "log") { let level = output.level; if (level === "Info") { level = chalk.bold.blueBright(level); @@ -42,10 +45,10 @@ function formatOutputColored(output) { level = chalk.bold.redBright(level); } - info = level + ' ' + chalk.gray(output.file) + ': '; + info = level + " " + chalk.gray(output.file) + ": "; - } else if (output.type === 'action') { - info = '[' + chalk.yellow(output.action) + '] '; + } else if (output.type === "action") { + info = "[" + chalk.yellow(output.action) + "] "; } return time + info + output.message; @@ -66,7 +69,7 @@ class Command { } async run(args, control) { - await this._handler.call(this, args, control); + await this._handler(args, control); } } @@ -76,8 +79,8 @@ class Command { * Resolves a string with either an instance name or an id into an integer * with the instance ID. * - * @param client - link to master server to query instance on. - * @param instanceName - string with name or id of instance. + * @param {module:lib/link.Link} client - link to master server to query instance on. + * @param {string} instanceName - string with name or id of instance. * @returns {number} instance ID. * @private */ @@ -108,8 +111,8 @@ async function resolveInstance(client, instanceName) { * Resolves a string with either an slave name or an id into an integer with * the slave ID. * - * @param client - link to master server to query slave on. - * @param slaveName - string with name or id of slave. + * @param {module:lib/link.Link} client - link to master server to query slave on. + * @param {string} slaveName - string with name or id of slave. * @returns {number} slave ID. * @private */ @@ -140,8 +143,8 @@ async function resolveSlave(client, slaveName) { * Resolves a string with either a role name or an id into an object * representing the role. * - * @param client - link to master server to query role on. - * @param roleName - string with name or id of role. + * @param {module:lib/link.Link} client - link to master server to query role on. + * @param {string} roleName - string with name or id of role. * @returns {Object} Role info. * @private */ @@ -199,10 +202,10 @@ commands.push(new Command({ yargs.option("id", { type: "number", nargs: 1, describe: "Slave id", default: null }); yargs.option("name", { type: "string", nargs: 1, describe: "Slave name", default: null }); yargs.option("generate-token", { - type: "boolean", nargs: 0, describe: "Generate authentication token", default: false + type: "boolean", nargs: 0, describe: "Generate authentication token", default: false, }); yargs.option("output", { - type: "string", nargs: 1, describe: "Path to output config (- for stdout)", default: "config-slave.json" + type: "string", nargs: 1, describe: "Path to output config (- for stdout)", default: "config-slave.json", }); }], handler: async function(args, control) { @@ -233,29 +236,29 @@ commands.push(new Command({ handler: async function(args, control) { let response = await link.messages.listInstances.send(control); console.log(asTable(response.list)); - } + }, })); commands.push(new Command({ - definition: ['create-instance', "Create an instance", (yargs) => { + definition: ["create-instance", "Create an instance", (yargs) => { // XXX TODO: set any specific options? - yargs.option('name', { + yargs.option("name", { type: "string", nargs: 1, describe: "Instance name", group: "Instance Config:", }); - yargs.option('id', { + yargs.option("id", { type: "number", nargs: 1, describe: "Instance ID", group: "Instance Config:", }); - yargs.option('base', { + yargs.option("base", { type: "boolean", nargs: 0, describe: "Specify this is a base config", - group: "Instance Config:" + group: "Instance Config:", }); }], handler: async function(args, control) { @@ -329,10 +332,10 @@ commands.push(new Command({ })); commands.push(new Command({ - definition: ['assign-instance', "Assign instance to a slave", (yargs) => { + definition: ["assign-instance", "Assign instance to a slave", (yargs) => { yargs.options({ - 'instance': { describe: "Instance to assign", nargs: 1, type: 'string', demandOption: true }, - 'slave': { describe: "Slave to assign to", nargs: 1, type: 'string', demandOption: true }, + "instance": { describe: "Instance to assign", nargs: 1, type: "string", demandOption: true }, + "slave": { describe: "Slave to assign to", nargs: 1, type: "string", demandOption: true }, }); }], handler: async function(args, control) { @@ -346,9 +349,9 @@ commands.push(new Command({ })); commands.push(new Command({ - definition: ['create-save', "Create a new save on an instance", (yargs) => { + definition: ["create-save", "Create a new save on an instance", (yargs) => { yargs.options({ - 'instance': { describe: "Instance to create on", nargs: 1, type: 'string', demandOption: true }, + "instance": { describe: "Instance to create on", nargs: 1, type: "string", demandOption: true }, }); }], handler: async function(args, control) { @@ -361,9 +364,9 @@ commands.push(new Command({ })); commands.push(new Command({ - definition: ['export-data', "Export item icons and locale from instance", (yargs) => { + definition: ["export-data", "Export item icons and locale from instance", (yargs) => { yargs.options({ - 'instance': { describe: "Instance to export from", nargs: 1, type: 'string', demandOption: true }, + "instance": { describe: "Instance to export from", nargs: 1, type: "string", demandOption: true }, }); }], handler: async function(args, control) { @@ -376,10 +379,10 @@ commands.push(new Command({ })); commands.push(new Command({ - definition: ['start-instance', "Start instance", (yargs) => { + definition: ["start-instance", "Start instance", (yargs) => { yargs.options({ - 'instance': { describe: "Instance to start", nargs: 1, type: 'string', demandOption: true }, - 'save': { describe: "Save load, defaults to latest", nargs: 1, type: 'string' }, + "instance": { describe: "Instance to start", nargs: 1, type: "string", demandOption: true }, + "save": { describe: "Save load, defaults to latest", nargs: 1, type: "string" }, }); }], handler: async function(args, control) { @@ -393,9 +396,9 @@ commands.push(new Command({ })); commands.push(new Command({ - definition: ['stop-instance', "Stop instance", (yargs) => { + definition: ["stop-instance", "Stop instance", (yargs) => { yargs.options({ - 'instance': { describe: "Instance to stop", nargs: 1, type: 'string', demandOption: true }, + "instance": { describe: "Instance to stop", nargs: 1, type: "string", demandOption: true }, }); }], handler: async function(args, control) { @@ -408,9 +411,9 @@ commands.push(new Command({ })); commands.push(new Command({ - definition: ['delete-instance', "Delete instance", (yargs) => { + definition: ["delete-instance", "Delete instance", (yargs) => { yargs.options({ - 'instance': { describe: "Instance to delete", nargs: 1, type: 'string', demandOption: true }, + "instance": { describe: "Instance to delete", nargs: 1, type: "string", demandOption: true }, }); }], handler: async function(args, control) { @@ -421,17 +424,17 @@ commands.push(new Command({ })); commands.push(new Command({ - definition: ['send-rcon', "Send RCON command", (yargs) => { + definition: ["send-rcon", "Send RCON command", (yargs) => { yargs.options({ - 'instance': { describe: "Instance to sent to", nargs: 1, type: 'string', demandOption: true }, - 'command': { describe: "command to send", nargs: 1, type: 'string', demandOption: true }, + "instance": { describe: "Instance to sent to", nargs: 1, type: "string", demandOption: true }, + "command": { describe: "command to send", nargs: 1, type: "string", demandOption: true }, }); }], handler: async function(args, control) { let response = await link.messages.sendRcon.send(control, { instance_id: await resolveInstance(control, args.instance), - command: args.command - }) + command: args.command, + }); // Factorio includes a newline in it's response output. process.stdout.write(response.result); @@ -466,7 +469,7 @@ commands.push(new Command({ let response = await link.messages.createRole.send(control, { name: args.name, description: args.description, - permissions: args.permissions + permissions: args.permissions, }); console.log(`Created role ID ${response.id}`); }, @@ -509,7 +512,7 @@ commands.push(new Command({ }); }], handler: async function(args, control) { - let role = await resolveRole(control, args.role) + let role = await resolveRole(control, args.role); await link.messages.deleteRole.send(control, { id: role.id }); }, })); @@ -623,7 +626,7 @@ class Control extends link.Link { // I don't like God classes, but the alternative of putting all this state // into global variables is not much better. constructor(connector) { - super('control', 'master', connector); + super("control", "master", connector); link.attachAllMessages(this); } @@ -647,7 +650,7 @@ class Control extends link.Link { } } - await this.connector.close(1001, "Control Quit") + await this.connector.close(1001, "Control Quit"); } } @@ -656,12 +659,12 @@ async function startControl() { yargs .scriptName("clusterctl") .usage("$0 [options]") - .option('config', { + .option("config", { nargs: 1, describe: "config file to get credentails from", default: "config-control.json", defaultDescription: "auto", - type: 'string', + type: "string", }) .command("control-config", "Manage Control config", config.configCommand) .wrap(yargs.terminalWidth()) @@ -684,7 +687,7 @@ async function startControl() { await controlConfig.load(JSON.parse(await fs.readFile(args.config))); } catch (err) { - if (err.code === 'ENOENT') { + if (err.code === "ENOENT") { console.log("Config not found, initializing new config"); await controlConfig.init(); @@ -730,10 +733,10 @@ async function startControl() { }); if (commands.has(commandName)) { - command = commands.get(commandName); + let command = commands.get(commandName); try { - await command.run(args, control) + await command.run(args, control); } catch (err) { if (err instanceof errors.CommandError) { @@ -761,7 +764,7 @@ module.exports = { _Control: Control, _commands: commands, -} +}; if (module === require.main) { diff --git a/docs/contributing.md b/docs/contributing.md index 7deb413b..9370d70d 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -11,6 +11,7 @@ Contents - [Workflow](#workflow) - [Starting a Feature Branch](#starting-a-feature-branch) + - [Checking your code for errors](#checking-your-code-for-errors) - [Submitting a Pull Request](#submitting-a-pull-request) - [Editing your Feature Branch](#editing-your-feature-branch) - [Resolving Merge Conflicts](#resolving-merge-conflicts) @@ -76,6 +77,18 @@ you've used before. But it's nice to have its name be somewhat descriptive of what it implements. +### Checking your code for errors + +There's automated tests and linting setup for the project. Please do +add tests for any code you add and run both: + + npm run test + npm run lint + +To check that your changes pass the integration tests as well as the +ESLint rules set up for the project. + + ### Submitting a Pull Request Once you've commited changes to your feature branch that you want to @@ -183,21 +196,13 @@ are at least a few things that have been agreed upon. JavaScript and Lua code should use double quoted strings (`"string"`) for all strings with the only exception that strings that contain double -quotes may use single quotes (i.e., `'String with "quotes"'`.) For +quotes may use single quotes (i.e., `'String with "quotes"'`). JavaScript template literals may also be used where appropriate. -There are places in the codebase that are using `'single'` quoted -strings. Should you be modifying such code use double quotes on new and -modified lines and leave the rest untouched. - ### Indenting -JavaScript code should be indented with tabs. There are places in the -codebase that are mixing tabs and spaces or uses only spaces for -indents. Should you be modifiying such code that is mixing spaces and -tabs as indending you should update the indenting to use tabs _on the -code you change_ while leaving the rest of the code untouched. +JavaScript code should be indented with tabs. Lua code should be indented with 4 spaces. diff --git a/docs/jsdoc.json b/docs/jsdoc.json index 54216916..19005b30 100644 --- a/docs/jsdoc.json +++ b/docs/jsdoc.json @@ -16,7 +16,6 @@ "clusterctl.js", "lib/", "plugins/", - "routes/", "routes.js" ], "excludePattern": "node_modules" diff --git a/lib/config/classes.js b/lib/config/classes.js index 1e3d1c39..ca369270 100644 --- a/lib/config/classes.js +++ b/lib/config/classes.js @@ -47,7 +47,7 @@ class InvalidField extends Error { }; function splitOn(separator, string) { let index = string.indexOf(separator); if (index === -1) { - return [string, '']; + return [string, ""]; } return [string.slice(0, index), string.slice(index + separator.length)]; } @@ -73,7 +73,7 @@ class Config extends events.EventEmitter { super(); if (!this.constructor._finalized) { - throw new Error(`Cannot instantiate incomplete Config class ${this.constructor.name}`) + throw new Error(`Cannot instantiate incomplete Config class ${this.constructor.name}`); } this._groups = new Map(); @@ -120,6 +120,8 @@ class Config extends events.EventEmitter { /** * Serialize the config to a plain JavaScript object + * + * @returns {Object} JSON serializable representation of the config. */ serialize() { let groups = [...this._unknownGroups.values()]; @@ -135,6 +137,9 @@ class Config extends events.EventEmitter { * * Updates all groups in the config with groups in the serialized * config passed. + * + * @param {Object} serializedConfig - Output from .serialize(). + * @param {boolean} notify - Invoke fieldChanged events if true. */ update(serializedConfig, notify) { this._validate(serializedConfig); @@ -150,34 +155,47 @@ class Config extends events.EventEmitter { /** * Get the value for a config field + * + * @param {string} name - Name of field to get. + * @returns {*} the value for the field. */ get(name) { - let [group, field] = splitOn('.', name); + let [group, field] = splitOn(".", name); return this.group(group).get(field); } /** * Set the value for a config field + * + * @param {string} name - Name of field to set. + * @param {*} value - the value to set on field */ set(name, value) { - let [group, field] = splitOn('.', name); - return this.group(group).set(field, value); + let [group, field] = splitOn(".", name); + this.group(group).set(field, value); } /** * Set property of an object value for a config field + * + * @param {string} name - Name of field to set property on. + * @param {string} prop - Name of property to set on field. + * @param {*} value - the value to set the property to. */ setProp(name, prop, value) { - let [group, field] = splitOn('.', name); - return this.group(group).setProp(field, prop, value); + let [group, field] = splitOn(".", name); + this.group(group).setProp(field, prop, value); } /** * Get the config group instance with the given name + * + * @param {string} name - Name of group to get. + * @returns {module:lib/config.ConfigGroup} config group. */ group(name) { if (!this._initialized) { - throw new Error(`${this.constructor.name} instance is uninitialized`) + throw new Error(`${this.constructor.name} instance is uninitialized`); } let group = this._groups.get(name); if (!group) { @@ -223,6 +241,8 @@ class Config extends events.EventEmitter { * * Adds the ConfigGroup to this config as a group that will get * initialized, loaded and serialized with this config class. + * + * @param {module:lib/config.ConfigGroup} group - class to register. */ static registerGroup(group) { this._initSubclass(); @@ -261,9 +281,9 @@ class ConfigGroup { * After the creation of the new config group you have to call * either .init() or .load() on it in order to fully initialize it. */ - constructor(serializedGroup) { + constructor() { if (!this.constructor._finalized) { - throw new Error(`Cannot instantiate incomplete ConfigGroup class ${this.constructor.name}`) + throw new Error(`Cannot instantiate incomplete ConfigGroup class ${this.constructor.name}`); } this._fields = new Map(); @@ -276,6 +296,8 @@ class ConfigGroup { * * Computes and assigns the initial values for all fields of the * config group. + * + * @param {module:lib/config.Config} config - Config group belongs to. */ async init(config) { this._config = config; @@ -287,7 +309,7 @@ class ConfigGroup { let value = null; if (typeof def.initial_value === "function") { - value = await def.initial_value() + value = await def.initial_value(); } else if (def.initial_value !== undefined) { value = def.initial_value; @@ -303,6 +325,10 @@ class ConfigGroup { * Loads all the values from the passed serialized version of the * group and then initializes any possible missing fields from it to * their default values. + * + * @param {module:lib/config.Config} config - Config group belongs to. + * @param {Object} serializedGroup - + * Result from a previous call to .serialize(). */ async load(config, serializedGroup) { this.update(serializedGroup, false); @@ -320,6 +346,9 @@ class ConfigGroup { /** * Get value for field + * + * @param {string} name - Name of field to get. + * @returns {*} Value stored for the field. */ get(name) { if (!this.constructor._definitions.has(name)) { @@ -335,6 +364,8 @@ class ConfigGroup { * The field must be defined for the config group and the value is * type checked against the field definition. * + * @param {string} name - Name of field to set. + * @param {*} value - Value to set for field. * @throws {module:lib/config.InvalidField} if field is not defined. * @throws {module:lib/config.InvalidValue} if value is not allowed for the field. */ @@ -350,7 +381,7 @@ class ConfigGroup { } if (def.enum && !def.enum.includes(value)) { - throw new InvalidValue(`Expected one of [${def.enum.join(', ')}], not ${value}`); + throw new InvalidValue(`Expected one of [${def.enum.join(", ")}], not ${value}`); } if (basicType(value) === "string") { @@ -364,24 +395,22 @@ class ConfigGroup { value = Number.parseFloat(value); } - } else if (def.type === "object" ) { + } else if (def.type === "object") { try { value = JSON.parse(value); } catch (err) { - throw new InvalidValue(`Error parsing value for ${name}: ${err.message}`) + throw new InvalidValue(`Error parsing value for ${name}: ${err.message}`); } } } if (value === null) { if (!def.optional) { - throw new InvalidValue(`Field ${name} cannot be null`) + throw new InvalidValue(`Field ${name} cannot be null`); } - } else { - if (basicType(value) !== def.type) { - throw new InvalidValue(`Expected type of ${name} to be ${def.type}, not ${basicType(value)}`); - } + } else if (basicType(value) !== def.type) { + throw new InvalidValue(`Expected type of ${name} to be ${def.type}, not ${basicType(value)}`); } let prev = this._fields.get(name); @@ -397,6 +426,9 @@ class ConfigGroup { * Update value of stored object field by setting the specified property * on it. The field must be defined as an object for the config group. * + * @param {string} name - Name of field to set property on. + * @param {string} prop - Name of property to set on field. + * @param {*} value - the value to set the property to. * @throws {module:lib/config.InvalidField} if field is not defined. * @throws {module:lib/config.InvalidValue} if field is not an object. */ @@ -430,6 +462,10 @@ class ConfigGroup { * * Overwrites the values of all the fields in this config group that * has a valid value in the passed serialized group with that value. + * + * @param {Object} serializedGroup - + * Result from a previous call to .serialize(). + * @param {boolean} notify - Invoke fieldChanged events if true. */ update(serializedGroup, notify) { if (basicType(serializedGroup) !== "object") { @@ -455,10 +491,9 @@ class ConfigGroup { if (value !== null && basicType(value) !== def.type) { continue; } - } else { - if (basicType(value) !== def.type) { - continue; - } + + } else if (basicType(value) !== def.type) { + continue; } if (def.enum && !def.enum.includes(value)) { @@ -475,6 +510,8 @@ class ConfigGroup { /** * Serialize the config to a plain JavaScript object + * + * @returns {Object} JSON serializable representation of the group. */ serialize() { let fields = {}; @@ -517,34 +554,34 @@ class ConfigGroup { /** * Define a new configuration field * - * The following properties are supported: - * - type: + * @param {Object} item - Field definition. + * @param {string} item.type - * string declaring the type of the config value, supports * boolean, string, number and object. - * - name: + * @param {string} item.name - * Name of the configuration field. Should follow the * lower_case_underscore style. - * - title: + * @param {string} item.title - * Text used to identify the config field in user interfaces. - * - description: + * @param {string} item.description - * Text used to describe the config field in user interfaces. - * - enum (optional): + * @param {Array=} item.enum - * Array of all values that are acceptible. Useful for making * enumerated values. - * - optional (optional): + * @param {boolean=} item.optional - * True if this field can be set to null. Defaults to false. - * - initial_value (optional): + * @param {*=} item.initial_value - * Value this config field should take in a newly initialized * config. This can also be an async function returning the * value to use. */ static define(item) { if (this._finalized) { - throw new Error(`Cannot define field for ConfigGroup class ${this.name} after it has been finalized`) + throw new Error(`Cannot define field for ConfigGroup class ${this.name} after it has been finalized`); } if (basicType(this.groupName) !== "string") { - throw new Error(`Expected ConfigGroup class ${this.name} to have the groupName property set to a string`) + throw new Error(`Expected ConfigGroup class ${this.name} to have the groupName property set to a string`); } this._initSubclass(); @@ -556,7 +593,7 @@ class ConfigGroup { if (!itemKeys.includes(prop)) { throw new Error(`${prop} is required when defining an field`); } - }) + }); // Validate properties let validTypes = ["boolean", "string", "number", "object"]; @@ -615,10 +652,11 @@ class ConfigGroup { throw new Error(`Config field ${item.name} has already been defined`); } - let computed = Object.assign({ + let computed = { optional: false, fullName: `${this.groupName}.${item.name}`, - }, item) + ...item, + }; if (!computed.optional && computed.initial_value === undefined) { throw new Error(`Non-optional field ${item.name} needs an initial_value`); @@ -632,7 +670,7 @@ class ConfigGroup { */ static finalize() { if (basicType(this.groupName) !== "string") { - throw new Error(`Expected ConfigGroup class ${this.name} to have the groupName property set to a string`) + throw new Error(`Expected ConfigGroup class ${this.name} to have the groupName property set to a string`); } this._initSubclass(); @@ -672,4 +710,4 @@ module.exports = { Config, ConfigGroup, PluginConfigGroup, -} +}; diff --git a/lib/config/definitions.js b/lib/config/definitions.js index 212c6813..02f2ee6d 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -15,7 +15,7 @@ const classes = require("./classes"); * @memberof module:lib/config */ class MasterGroup extends classes.ConfigGroup { } -MasterGroup.groupName = "master" +MasterGroup.groupName = "master"; MasterGroup.define({ name: "database_directory", title: "Database directory", @@ -28,7 +28,7 @@ MasterGroup.define({ title: "HTTP Port", description: "Port to listen for HTTP connections on, set to null to not listen for HTTP connections.", type: "number", - optional: true + optional: true, }); MasterGroup.define({ name: "https_port", @@ -139,7 +139,7 @@ SlaveGroup.define({ name: "factorio_directory", description: "Path to directory to look for factorio installs", type: "string", - initial_value: "factorio" + initial_value: "factorio", }); SlaveGroup.define({ name: "instances_directory", @@ -189,7 +189,7 @@ SlaveConfig.registerGroup(SlaveGroup); * @memberof module:lib/config */ class InstanceGroup extends classes.ConfigGroup { } -InstanceGroup.groupName = "instance" +InstanceGroup.groupName = "instance"; InstanceGroup.define({ name: "name", type: "string", @@ -314,6 +314,7 @@ function validateGroup(pluginInfo, groupName) { /** * Registers the config groups for the provided plugin infos * + * @param {Array} pluginInfos - Array of plugin info objects. * @memberof module:lib/config */ function registerPluginConfigGroups(pluginInfos) { @@ -362,6 +363,7 @@ function finalizeConfigs() { * Can be passed to yargs.command to implement a config command. Use * handleConfigCommand to do the requested action. * + * @param {Object} yargs - yargs command builder. * @memberof module:lib/config */ function configCommand(yargs) { @@ -379,6 +381,9 @@ function configCommand(yargs) { * * Handle the actions that are made available by configCommand. * + * @param {Object} args - yargs args object. + * @param {module:lib/config.Config} instance - Config instance. + * @param {string} configPath - Path to configuration file. * @memberof module:lib/config */ async function handleConfigCommand(args, instance, configPath) { @@ -438,4 +443,4 @@ module.exports = { finalizeConfigs, configCommand, handleConfigCommand, -} +}; diff --git a/lib/config/index.js b/lib/config/index.js index f591436e..fa9b93b2 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -3,7 +3,8 @@ * @author Hornwitser * @module lib/config */ +"use strict"; module.exports = { ...require("./classes"), ...require("./definitions"), -} +}; diff --git a/lib/database.js b/lib/database.js index 85894de0..74b59853 100644 --- a/lib/database.js +++ b/lib/database.js @@ -21,7 +21,7 @@ const { basicType } = require("lib/helpers"); function mapToObject(map) { let obj = {}; for (let [key, value] of map) { - if (typeof key !== 'string') { + if (typeof key !== "string") { throw new Error( `Expected all keys to be string but got ${typeof key}` ); @@ -105,11 +105,11 @@ async function loadJsonArrayAsMap(filePath) { let map = new Map(); for (let element of parsed) { if (basicType(element) !== "object") { - throw new Error(`Expected all elements to be objects`); + throw new Error("Expected all elements to be objects"); } if (element.id === undefined) { - throw new Error(`Expected all elements to have an id property`); + throw new Error("Expected all elements to have an id property"); } map.set(element.id, element); @@ -144,7 +144,6 @@ async function saveMapAsJsonArray(filePath, map) { } - function checkName(name) { if (typeof name !== "string") { throw new Error("name must be a string"); @@ -232,7 +231,7 @@ class ItemDatabase { checkName(name); if (!this._items.has(name)) { - return 0 + return 0; } return this._items.get(name); @@ -287,4 +286,4 @@ module.exports = { loadJsonArrayAsMap, saveMapAsJsonArray, ItemDatabase, -} +}; diff --git a/lib/errors.js b/lib/errors.js index 2b9a6bdd..7ec888ea 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -2,6 +2,7 @@ * Errors thrown in Clusterio * @module */ +"use strict"; /** * Thrown by commands when they fail @@ -71,4 +72,4 @@ module.exports = { StartupError, EnvironmentError, PluginError, -} +}; diff --git a/lib/factorio/export.js b/lib/factorio/export.js index 4c2d08cb..c1ac3da5 100644 --- a/lib/factorio/export.js +++ b/lib/factorio/export.js @@ -21,7 +21,7 @@ const buildMod = require("../../build-mod"); */ async function generateExportMod(server) { let dependencies = []; - let splitter = /^(.*)_(\d+\.\d+\.\d+)(\.zip)?$/ + let splitter = /^(.*)_(\d+\.\d+\.\d+)(\.zip)?$/; for (let entry of await fs.readdir(server.writePath("mods"))) { let match = splitter.exec(entry); if (match && match[1] !== "export") { @@ -106,8 +106,11 @@ async function loadFile(server, modVersions, modPath) { * * @param {module:lib/factorio.FactorioServer} server - * The server to generate the export mod for. + * @param {Map} modVersions - + * Mapping of mod name to versions to get icons from. * @param {Array} items - Array of item prototypes. - * @return {itemSheet: Jimp, itemData: Map} Item spritesheet and metadata. + * @return {{itemSheet: Jimp, itemData: Map}} + * Item spritesheet and metadata. * @memberof module:lib/factorio * @private * @inner @@ -193,6 +196,7 @@ async function exportItems(server, modVersions, items) { iconLayer.scale(realScale); } + // eslint-disable-next-line no-loop-func iconLayer.scan(0, 0, iconLayer.bitmap.width, iconLayer.bitmap.height, function(x, y, sidx) { x += xs; y += ys; @@ -248,7 +252,7 @@ async function exportItems(server, modVersions, items) { iconSheet.crop(0, 0, width, Math.ceil(pos / (width / size)) * size); - return {iconSheet, itemData}; + return { iconSheet, itemData }; } /** @@ -259,9 +263,10 @@ async function exportItems(server, modVersions, items) { * * @param {module:lib/factorio.FactorioServer} server - * The server to export the locale from. - * @param modVersions {Map} modVersions - + * @param {Map} modVersions - * Mapping of mod name to version to export locale from. - * @param modOrder {Array} modOrder - Load order of the mods. + * @param {Array} modOrder - Load order of the mods. + * @param {string} languageCode - Language to export locale for. * @returns {Map} merged locale information * @memberof module:lib/factorio * @private @@ -338,7 +343,7 @@ async function exportData(server) { throw new Error("No items got exported"); } - let {iconSheet, itemData} = await exportItems(server, modVersions, items); + let { iconSheet, itemData } = await exportItems(server, modVersions, items); let locale = await exportLocale(server, modVersions, modOrder, "en"); // Free up the memory used by zip files loaded during the export. diff --git a/lib/factorio/index.js b/lib/factorio/index.js index 5faa36f6..f6104a77 100644 --- a/lib/factorio/index.js +++ b/lib/factorio/index.js @@ -3,8 +3,9 @@ * @module lib/factorio * @author Hornwitser */ +"use strict"; module.exports = { ...require("lib/factorio/export"), - ...require('lib/factorio/patch'), - ...require('lib/factorio/server'), + ...require("lib/factorio/patch"), + ...require("lib/factorio/server"), }; diff --git a/lib/factorio/patch.js b/lib/factorio/patch.js index e7545be1..6742a21f 100644 --- a/lib/factorio/patch.js +++ b/lib/factorio/patch.js @@ -27,6 +27,7 @@ const knownScenarios = { * Returns the name of the folder that all files in the zip file is * contained in. Throws an error if there are multiple such folders. * + * @param {module:jszip.JSZip} zip - Zip to search through. * @returns {string} name of the root folder. * @memberof module:lib/factorio * @private @@ -44,12 +45,12 @@ function findRoot(zip) { if (root === null) { root = pathRoot; } else if (root !== pathRoot) { - throw new Error(`Zip contains multiple root folders`); + throw new Error("Zip contains multiple root folders"); } }); if (root === null) { - throw new Error(`Empty zip file`); + throw new Error("Empty zip file"); } return root; @@ -60,19 +61,20 @@ function findRoot(zip) { * Generates control.lua code for loading the Clusterio modules * * @param {Object} patchInfo - The patch info files's json content + * @returns {string} Generated control.lua code. * @memberof module:lib/factorio * @private * @inner */ function generateLoader(patchInfo) { let lines = [ - '-- Auto generated scenario module loader created by Clusterio', - '-- Modifications to this file will be lost when loaded in Clusterio', + "-- Auto generated scenario module loader created by Clusterio", + "-- Modifications to this file will be lost when loaded in Clusterio", `clusterio_patch_number = ${patchInfo.patch_number}`, - '', + "", 'local event_handler = require("event_handler")', - '', - '-- Scenario modules', + "", + "-- Scenario modules", ]; for (let moduleName of patchInfo["scenario"]["modules"]) { @@ -80,8 +82,8 @@ function generateLoader(patchInfo) { } lines.push(...[ - '', - '-- Clusterio modules', + "", + "-- Clusterio modules", ]); for (let module of patchInfo["modules"]) { @@ -97,9 +99,9 @@ function generateLoader(patchInfo) { } // End last line with a newline - lines.push(''); + lines.push(""); - return lines.join('\n'); + return lines.join("\n"); } /** @@ -188,7 +190,7 @@ function reorderDependencies(modules) { if (cycle.includes(module.name)) { cycle.push(module.name); - cycle.splice(0, cycle.indexOf(module.name)) + cycle.splice(0, cycle.indexOf(module.name)); throw new Error(`Module dependency loop detected: ${cycle.join(" -> ")}`); } @@ -251,9 +253,9 @@ async function patch(savePath, modules) { let moduleInfo = { "name": module.name, "files": [], - } + }; - let dirs = [[module.path, '']]; + let dirs = [[module.path, ""]]; while (dirs.length) { let [dir, relativeDir] = dirs.pop(); for (let entry of await fs.readdir(dir, { withFileTypes: true })) { @@ -282,7 +284,7 @@ async function patch(savePath, modules) { root.file("clusterio.json", JSON.stringify(patchInfo, null, 4)); // Write back the save - let stream = zip.generateNodeStream({compression: "DEFLATE"}); + let stream = zip.generateNodeStream({ compression: "DEFLATE" }); let pipe = stream.pipe(fs.createWriteStream(savePath)); await events.once(pipe, "finish"); } @@ -294,4 +296,4 @@ module.exports = { _findRoot: findRoot, _generateLoader: generateLoader, _reorderDependencies: reorderDependencies, -} +}; diff --git a/lib/factorio/server.js b/lib/factorio/server.js index 30843c19..9b2a033b 100644 --- a/lib/factorio/server.js +++ b/lib/factorio/server.js @@ -16,6 +16,7 @@ const errors = require("lib/errors"); * Determines the version of Factorio the datadir is pointing to by * reading the changelog.txt in it. * + * @param {string} changelogPath - Path to changelog.txt. * @memberof module:lib/factorio * @private * @inner @@ -47,6 +48,9 @@ async function getVersion(changelogPath) { /** * Comparison function for sorting Factorio version strings * + * @param {string} a - Version to compare. + * @param {string} b - Version to compare. + * @returns {number} 1 if a < b, 0 if a = b and -1 if a > b. * @memberof module:lib/factorio * @private * @inner @@ -74,6 +78,11 @@ function versionOrder(a, b) { * Searches the given factorio dir for an installation of Factorio of * the target version. * + * @param {string} factorioDir - Path to Factorio installation dir(s). + * @param {string} targetVersion - + * Version to look for, supports the special value "latest" for the + * latest version available. + * @returns {Array} Array with path to data dir and version found. * @memberof module:lib/factorio * @private * @inner @@ -140,7 +149,7 @@ function randomDynamicPort() { const start = 49152; const end = 65535 + 1; - return Math.floor(Math.random() * (end - start) + start) + return Math.floor(Math.random() * (end - start) + start); } /** @@ -157,10 +166,10 @@ function randomDynamicPort() { */ async function generatePassword(length) { function validChar(byte) { - const ranges = ['az', 'AZ', '09']; - return ranges.some(range => + const ranges = ["az", "AZ", "09"]; + return ranges.some(range => ( range.codePointAt(0) <= byte && byte <= range.codePointAt(1) - ); + )); } let randomBytesAsync = util.promisify(crypto.randomBytes); @@ -206,7 +215,7 @@ class LineSplitter { */ constructor(callback) { this._callback = callback; - this._partial = null + this._partial = null; } /** @@ -278,12 +287,12 @@ function parseOutput(line, source) { let output = { source, received: Date.now(), - } + }; // There are three broad categories of output from Factorio, the first kind // starts with the seconds since the server started and has a format of // " 0.704 message" - const secRegex = /^ {0,3}(\d+\.\d+) (.*)$/ + const secRegex = /^ {0,3}(\d+\.\d+) (.*)$/; let secMatch = secRegex.exec(line); if (secMatch) { output.format = "seconds"; @@ -293,7 +302,7 @@ function parseOutput(line, source) { // A seconds output has two different kinds of formats: The first is a // log level and source location and has a format of // "Level File.cpp:123: message" - const secLogRegex = /^(\w+) (\w+\.cpp:\d+): (.*)$/ + const secLogRegex = /^(\w+) (\w+\.cpp:\d+): (.*)$/; let secLogMatch = secLogRegex.exec(secContent); if (secLogMatch) { output.type = "log"; @@ -310,7 +319,7 @@ function parseOutput(line, source) { // The second category of output starts with a date stamp of the format // "yyyy-mm-dd hh:mm:ss message" } else { - const dateRegex = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d) (.*)$/ + const dateRegex = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d) (.*)$/; let dateMatch = dateRegex.exec(line); if (dateMatch) { output.format = "date"; @@ -319,7 +328,7 @@ function parseOutput(line, source) { // A date output has two general formats. The first is an action // followed by a message and has a format of "[ACTION] message" - const dateActionRegex = /^\[(\w+)\] (.*)$/ + const dateActionRegex = /^\[(\w+)\] (.*)$/; let dateActionMatch = dateActionRegex.exec(dateContent); if (dateActionMatch) { output.type = "action"; @@ -355,29 +364,29 @@ function parseOutput(line, source) { const outputHeuristics = [ // Message indicating the RCON interface has started { - filter: { type: 'log', message: /^Starting RCON interface/ }, + filter: { type: "log", message: /^Starting RCON interface/ }, action: function(output) { - this._startRcon().catch((err) => { this.emit('error', err); }); + this._startRcon().catch((err) => { this.emit("error", err); }); }, }, // Message indicating the server is done starting up { - filter: { type: 'log', message: /^updateTick\(\d+\) changing state from\(CreatingGame\) to\(InGame\)$/ }, + filter: { type: "log", message: /^updateTick\(\d+\) changing state from\(CreatingGame\) to\(InGame\)$/ }, action: function(output) { - this._notifyGameReady().catch((err) => { this.emit('error', err); }); - } + this._notifyGameReady().catch((err) => { this.emit("error", err); }); + }, }, // Message indicating the server has started an autosave { filter: { - type: 'log', + type: "log", message: /^Saving to _autosave\d+ \((non-)?blocking\)\.$/, }, action: function(output) { let name = /^Saving to (_autosave\d+) /.exec(output.message)[1]; - this.emit('_autosave', name); + this.emit("_autosave", name); }, }, @@ -395,7 +404,7 @@ const outputHeuristics = [ // Message indicating the server has finished saving { filter: { - type: 'log', + type: "log", message: [ "Saving finished", @@ -404,18 +413,18 @@ const outputHeuristics = [ ], }, action: function(output) { - this.emit('_saved'); - } + this.emit("_saved"); + }, }, // Message indicating the server is shutting down { filter: { - type: 'generic', - message: /^Quitting: / + type: "generic", + message: /^Quitting: /, }, action: function(output) { - this.emit('_quitting'); + this.emit("_quitting"); // On windows the Factorio server will wait for input on the console input buffer // when an error occurs. Since we can't send input there we terminate the server @@ -423,14 +432,14 @@ const outputHeuristics = [ if (process.platform === "win32" && output.message === "Quitting: multiplayer error.") { this._server.kill(); } - } + }, }, // Messages that might be tha cause of an unexpected shutdown. { filter: { - type: 'log', - level: 'Error', + type: "log", + level: "Error", message: [ "MultiplayerManager failed: \"Binding IPv4 socket failed: Permission denied\"", "MultiplayerManager failed: Host address is already in use.", @@ -442,8 +451,8 @@ const outputHeuristics = [ }, { filter: { - type: 'log', - level: 'Error', + type: "log", + level: "Error", message: [ "Can't bind socket: Address already in use", "Can't bind socket: Permission denied", @@ -515,22 +524,22 @@ class FactorioServer extends events.EventEmitter { // Array of possible causes for an unexpected shutdown this._unexpected = []; - this._stdout = new LineSplitter((line) => { this._handleOutput(line, 'stdout'); }); - this._stderr = new LineSplitter((line) => { this._handleOutput(line, 'stderr'); }); + this._stdout = new LineSplitter((line) => { this._handleOutput(line, "stdout"); }); + this._stderr = new LineSplitter((line) => { this._handleOutput(line, "stderr"); }); // Track autosaving this._runningAutosave = null; this.on("_autosave", function(name) { this.emit("autosave-start", name); this._runningAutosave = name; - }) + }); this.on("_saved", function() { if (this._runningAutosave) { this.emit("autosave-finished", this._runningAutosave); this._runningAutosave = null; } - }) + }); } _check(expectedStates) { @@ -542,40 +551,40 @@ class FactorioServer extends events.EventEmitter { } async _handleIpc(line) { - let channelEnd = line.indexOf('?'); + let channelEnd = line.indexOf("?"); if (channelEnd === -1) { throw new Error(`Malformed IPC line "${line.toString()}"`); } let channel = line .slice(6, channelEnd) - .toString('utf-8') - .replace(/\\x([0-9a-f]{2})/g, (match, p1) => + .toString("utf-8") + .replace(/\\x([0-9a-f]{2})/g, (match, p1) => ( String.fromCharCode(parseInt(p1, 16)) - ) + )) ; - let type = line.slice(channelEnd + 1, channelEnd + 2).toString('utf-8'); + let type = line.slice(channelEnd + 1, channelEnd + 2).toString("utf-8"); let content; - if (type === 'j') { - content = JSON.parse(line.slice(channelEnd + 2).toString('utf-8')); + if (type === "j") { + content = JSON.parse(line.slice(channelEnd + 2).toString("utf-8")); - } else if (type === 'f') { - let fileName = line.slice(channelEnd + 2).toString('utf-8'); - let filePath = this.writePath('script-output', fileName); + } else if (type === "f") { + let fileName = line.slice(channelEnd + 2).toString("utf-8"); + let filePath = this.writePath("script-output", fileName); // Prevent malicious names if (/[\\/\0]/.test(fileName)) { - throw new Error(`Invalid IPC file name '${fileName}'`) + throw new Error(`Invalid IPC file name '${fileName}'`); } - if (fileName.endsWith('.json')) { - content = JSON.parse(await fs.readFile(filePath, 'utf-8')); + if (fileName.endsWith(".json")) { + content = JSON.parse(await fs.readFile(filePath, "utf-8")); await fs.unlink(filePath); } else { - let format = fileName.slice(fileName.lastIndexOf('.') + 1); - throw new Error(`Unknown IPC file format '${format}'`) + let format = fileName.slice(fileName.lastIndexOf(".") + 1); + throw new Error(`Unknown IPC file format '${format}'`); } } else { @@ -589,13 +598,13 @@ class FactorioServer extends events.EventEmitter { _handleOutput(line, source) { if (line.slice(0, 6).equals(Buffer.from("\f$ipc:"))) { - this._handleIpc(line).catch(err => this.emit('error', err)); + this._handleIpc(line).catch(err => this.emit("error", err)); return; } this.emit(source, line); - let output = parseOutput(line.toString('utf-8'), source); + let output = parseOutput(line.toString("utf-8"), source); heuristicLoop: for (let heuristic of outputHeuristics) { for (let [name, expected] of Object.entries(heuristic.filter)) { if (!Object.hasOwnProperty.call(output, name)) { @@ -610,10 +619,8 @@ class FactorioServer extends events.EventEmitter { if (!expected.includes(output[name])) { continue heuristicLoop; } - } else { - if (expected !== output[name]) { - continue heuristicLoop; - } + } else if (expected !== output[name]) { + continue heuristicLoop; } } @@ -621,7 +628,7 @@ class FactorioServer extends events.EventEmitter { heuristic.action.call(this, output); } - this.emit('output', output); + this.emit("output", output); } async _startRcon() { @@ -642,8 +649,8 @@ class FactorioServer extends events.EventEmitter { }; this._rconClient = new rconClient.Rcon(config); - this._rconClient.on("error", err => { /* Ignore */ }); - this._rconClient.on('authenticated', () => { this._rconReady = true; this.emit('rcon-ready'); }); + this._rconClient.on("error", () => { /* Ignore */ }); + this._rconClient.on("authenticated", () => { this._rconReady = true; this.emit("rcon-ready"); }); this._rconClient.on("end", () => { this._rconClient = null; this._rconReady = false; @@ -685,37 +692,37 @@ class FactorioServer extends events.EventEmitter { } _attachStdio() { - this._server.stdout.on('data', chunk => { this._stdout.data(chunk); }); - this._server.stdout.on('close', () => { this._stdout.end(); }); - this._server.stderr.on('data', chunk => { this._stderr.data(chunk); }); - this._server.stderr.on('close', () => { this._stderr.end(); }); + this._server.stdout.on("data", chunk => { this._stdout.data(chunk); }); + this._server.stdout.on("close", () => { this._stdout.end(); }); + this._server.stderr.on("data", chunk => { this._stderr.data(chunk); }); + this._server.stderr.on("close", () => { this._stderr.end(); }); } _watchExit() { - this._server.on('exit', (code, signal) => { + this._server.on("exit", (code, signal) => { if (this._state !== "stopping") { if (this._unexpected.length === 0) { - if (signal === 'SIGKILL') { - this.emit('error', new errors.EnvironmentError( + if (signal === "SIGKILL") { + this.emit("error", new errors.EnvironmentError( "Factorio server was unexpectedly killed, is the system low on memory?" )); } else { let msg; if (code !== null) { - msg = `Factorio server unexpectedly shut down with code ${code}` + msg = `Factorio server unexpectedly shut down with code ${code}`; } else { msg = `Factorio server was unexpectedly shut by down by signal ${signal}`; } - this.emit('error', new Error(msg)); + this.emit("error", new Error(msg)); } } else if (this._unexpected.length === 1) { - this.emit('error', new Error(this._unexpected[0])); + this.emit("error", new Error(this._unexpected[0])); } else { - this.emit('error', new Error( + this.emit("error", new Error( "Factorio server unexpectedly shut down. Possible causes:\n- "+ this._unexpected.join("\n- ") )); @@ -737,12 +744,12 @@ class FactorioServer extends events.EventEmitter { async _notifyGameReady() { this._gameReady = true; - this.emit('game-ready'); + this.emit("game-ready"); } async _waitForReady() { if (!this._gameReady) { - await events.once(this, 'game-ready'); + await events.once(this, "game-ready"); } } @@ -761,14 +768,14 @@ class FactorioServer extends events.EventEmitter { await this._writeConfigIni(); this._server = child_process.spawn( - this.dataPath('..', 'bin', 'x64', 'factorio'), + this.dataPath("..", "bin", "x64", "factorio"), [ - '--config', this.writePath('config.ini'), - '--create', this.writePath('saves', name), + "--config", this.writePath("config.ini"), + "--create", this.writePath("saves", name), ], { detached: true, - stdio: 'pipe', + stdio: "pipe", } ); @@ -776,7 +783,7 @@ class FactorioServer extends events.EventEmitter { this._server.once("exit", () => this.emit("exit")); try { - let [code, signal] = await events.once(this._server, 'exit'); + let [code, signal] = await events.once(this._server, "exit"); if (code !== 0) { throw new Error(`Factorio exited with status ${code}`); } @@ -799,17 +806,17 @@ class FactorioServer extends events.EventEmitter { await this._writeConfigIni(); this._server = child_process.spawn( - this.dataPath('..', 'bin', 'x64', 'factorio'), + this.dataPath("..", "bin", "x64", "factorio"), [ - '--config', this.writePath('config.ini'), - '--start-server', this.writePath('saves', save), - '--port', this.gamePort, - '--rcon-port', this.rconPort, - '--rcon-password', this.rconPassword, + "--config", this.writePath("config.ini"), + "--start-server", this.writePath("saves", save), + "--port", this.gamePort, + "--rcon-port", this.rconPort, + "--rcon-password", this.rconPassword, ], { detached: true, - stdio: 'pipe', + stdio: "pipe", } ); @@ -832,17 +839,17 @@ class FactorioServer extends events.EventEmitter { await this._writeConfigIni(); this._server = child_process.spawn( - this.dataPath('..', 'bin', 'x64', 'factorio'), + this.dataPath("..", "bin", "x64", "factorio"), [ - '--config', this.writePath('config.ini'), - '--start-server-load-scenario', scenario, - '--port', this.gamePort, - '--rcon-port', this.rconPort, - '--rcon-password', this.rconPassword, + "--config", this.writePath("config.ini"), + "--start-server-load-scenario", scenario, + "--port", this.gamePort, + "--rcon-port", this.rconPort, + "--rcon-password", this.rconPassword, ], { detached: true, - stdio: 'pipe', + stdio: "pipe", } ); @@ -865,13 +872,13 @@ class FactorioServer extends events.EventEmitter { * @returns {string} response from server. */ async sendRcon(message, expectEmpty) { - this._check(["running", "stopping"]) + this._check(["running", "stopping"]); if (!this._rconReady) { - await events.once(this, 'rcon-ready'); + await events.once(this, "rcon-ready"); } let response = await this._rconClient.send(message); - if (expectEmpty && response !== '') { + if (expectEmpty && response !== "") { throw new Error(`Expected empty response but got "${response}"`); } return response; @@ -923,7 +930,7 @@ class FactorioServer extends events.EventEmitter { // If RCON is not yet fully connected that operation needs to // complete before the RCON connection can be closed cleanly. if (!this._rconReady) { - await events.once(this, 'rcon-ready'); + await events.once(this, "rcon-ready"); } await this._rconClient.end(); @@ -942,16 +949,16 @@ class FactorioServer extends events.EventEmitter { // stop if it's stuck with an infinite lua code loop. In either // case there's no recovering from it. if (process.platform !== "win32") { - this.on('_quitting', setAlive); - await events.once(this._server, 'exit'); + this.on("_quitting", setAlive); + await events.once(this._server, "exit"); clearTimeout(timeoutId); - this.off('_quitting', setAlive); + this.off("_quitting", setAlive); // On windows the process is terminated immediately, but to keep // ordering wait until after the exit event here. } else { - await events.once(this._server, 'exit'); + await events.once(this._server, "exit"); } } @@ -981,14 +988,14 @@ class FactorioServer extends events.EventEmitter { this._check(["running"]); let check = await this.sendRcon("/sc rcon.print('disabled')"); - if (check === 'disabled\n') { + if (check === "disabled\n") { return false; } // Factorio will print a warning to the console and ask for the command // to be runned again before disabling achievements and allowing commands. check = await this.sendRcon("/sc rcon.print('disabled')"); - if (check === 'disabled\n') { + if (check === "disabled\n") { return true; } @@ -1001,6 +1008,9 @@ class FactorioServer extends events.EventEmitter { * Creates a path using path.join with the given parts that's relative to * the data directory of the Factorio server. Not valid before init has * been called. + * + * @param {...string} parts - Extra parts to add to the data path. + * @returns {string} Data directory path. */ dataPath(...parts) { return path.join(this._dataDir, ...parts); @@ -1011,6 +1021,9 @@ class FactorioServer extends events.EventEmitter { * * Creates a path using path.join with the given parts that's relative to * the write directory of the Factorio server. + * + * @param {...string} parts - Extra parts to add to the write path. + * @returns {string} Write directory path. */ writePath(...parts) { return path.join(this._writeDir, ...parts); @@ -1032,7 +1045,7 @@ class FactorioServer extends events.EventEmitter { path: { "read-data": this.dataPath(), "write-data": this.writePath(), - } + }, }); await fs.outputFile(this.writePath("config.ini"), content); } diff --git a/lib/fileOps/fileOps.js b/lib/fileOps/fileOps.js index ed42b794..07415560 100644 --- a/lib/fileOps/fileOps.js +++ b/lib/fileOps/fileOps.js @@ -1,6 +1,7 @@ /** * @module lib/fileOps */ +"use strict"; const fs = require("fs-extra"); const path = require("path"); @@ -33,4 +34,4 @@ async function getNewestFile(directory, filter = (name) => true) { module.exports = { getNewestFile, -} +}; diff --git a/lib/fileOps/index.js b/lib/fileOps/index.js index 5df8775d..0e9bcbe8 100644 --- a/lib/fileOps/index.js +++ b/lib/fileOps/index.js @@ -1,2 +1,3 @@ +"use strict"; const fileOps = require("./fileOps"); -module.exports = fileOps; \ No newline at end of file +module.exports = fileOps; diff --git a/lib/generateSSLcert.js b/lib/generateSSLcert.js index 4fbafd1b..fee1b50b 100644 --- a/lib/generateSSLcert.js +++ b/lib/generateSSLcert.js @@ -1,3 +1,4 @@ +"use strict"; const forge = require("node-forge"); const fs = require("fs-extra"); const util = require("util"); @@ -17,7 +18,7 @@ module.exports = async function({ log("Generating SSL certificate with "+bits+" bits at "+sslCertPath); let startNs = hrtime(); - let keypair = await util.promisify(forge.pki.rsa.generateKeyPair)({bits: bits, e: 0x10001}); + let keypair = await util.promisify(forge.pki.rsa.generateKeyPair)({ bits: bits, e: 0x10001 }); log("Generated 2048bit keypair in: "+(Number(hrtime()-startNs) / 1e6)+" ms"); startNs = hrtime(); @@ -41,4 +42,4 @@ module.exports = async function({ await fs.outputFile(sslCertPath, forge.pki.certificateToPem(cert)); log("Generated certificate in: "+(Number(hrtime()-startNs) / 1e6)+" ms"); -} +}; diff --git a/lib/hash.js b/lib/hash.js index 9bb694ed..147ebb50 100644 --- a/lib/hash.js +++ b/lib/hash.js @@ -2,25 +2,26 @@ * Hashing functions * @module */ -const crypto = require('crypto'); -const fs = require('fs'); +"use strict"; +const crypto = require("crypto"); +const fs = require("fs"); /** * Returns a promise that resolves to the SHA1 hash of the stream given * - * @param stream - Node stream of the content to hash. + * @param {Stream} stream - Node stream of the content to hash. * @returns {Promise} hash of the stream. */ function hashStream(stream) { return new Promise(function(resolve, reject) { - let hasher = crypto.createHash('sha1') - hasher.setEncoding('hex') - hasher.on('finish', function() { + let hasher = crypto.createHash("sha1"); + hasher.setEncoding("hex"); + hasher.on("finish", function() { resolve(hasher.read()); }); - stream.on('error', function(error) { + stream.on("error", function(error) { // the docs doesn't say anything about what's passed on error reject(error); }); @@ -42,4 +43,4 @@ function hashFile(path) { module.exports = { hashStream, hashFile, -} +}; diff --git a/lib/helpers.js b/lib/helpers.js index afc1f9bf..8fa63587 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -2,6 +2,7 @@ * Collection of small utilites that are useful in multiple places. * @module */ +"use strict"; /** * Return a string describing the type of the value passed @@ -9,7 +10,7 @@ * Works the same as typeof, excpet that null and array types get their * own string. * - * @param value - value to return the type of. + * @param {*} value - value to return the type of. * @returns {string} basic type of the value passed. */ function basicType(value) { @@ -20,4 +21,4 @@ function basicType(value) { module.exports = { basicType, -} +}; diff --git a/lib/link/connectors.js b/lib/link/connectors.js index 271fa326..a7997f0a 100644 --- a/lib/link/connectors.js +++ b/lib/link/connectors.js @@ -94,7 +94,9 @@ class WebSocketBaseConnector extends events.EventEmitter { /** * Send a message over the socket * - * @returns the sequence number of the message sent + * @param {string} type - Message type to send. + * @param {Object} data - Data to send with message. + * @returns {number} the sequence number of the message sent */ send(type, data) { if (!["handshake", "connected", "closing"].includes(this._state)) { @@ -181,6 +183,9 @@ class WebSocketClientConnector extends WebSocketBaseConnector { * * Used in the register function in order to send the register handshake * message over the WebSocket. + * + * @param {string} type - Message type to send. + * @param {Object} data - Data to send with message. */ sendHandshake(type, data) { this._check(["handshake"]); @@ -191,6 +196,9 @@ class WebSocketClientConnector extends WebSocketBaseConnector { * Close the connection with the given reason. * * Sends a close frame and disconnects the connector. + * + * @param {number} code - WebSocket close code. + * @param {string} reason - WebSocket close reason. */ async close(code, reason) { if (this._state === "new") { @@ -226,7 +234,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector { url.pathname += "api/socket"; // Open WebSocket to master - console.log(`SOCKET | connecting to ${url}`) + console.log(`SOCKET | connecting to ${url}`); this._socket = new WebSocket(url, { // For now we do not verify TLS certificates since the default setup is @@ -261,7 +269,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector { } let delay = Math.random() * this._reconnectDelay; - console.log(`SOCKET | waiting ${delay.toLocaleString("en", { maximumFractionDigits: 2 })} seconds for reconnect`) + console.log(`SOCKET | waiting ${delay.toLocaleString("en", { maximumFractionDigits: 2 })} seconds for reconnect`); this._reconnectId = setTimeout(() => { this._reconnectId = null; this._doConnect(); @@ -302,18 +310,6 @@ class WebSocketClientConnector extends WebSocketBaseConnector { console.error("SOCKET | Error:", err); }); - /* - this._socket.on("unexpected-response", (req, res) => { - let data = ""; - res.on('data', (chunk) => { data += chunk; }); - res.on('end', () => { - console.error(`SOCKET | Unexpeced Response ${res.statusCode}`); - console.error(data); - this._socket.emit("error", new Error(`Unexpected Response ${res.statusCode}`)); - }); - }); - */ - this._socket.on("open", () => { console.log("SOCKET | Open"); }); @@ -356,7 +352,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector { } let { seq, type, data } = message; - if (type == 'hello') { + if (type == "hello") { console.log(`SOCKET | received hello from master version ${data.version}`); this.emit("hello", data); if (this._sessionToken) { @@ -369,7 +365,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector { this.register(); } - } else if (type === 'ready') { + } else if (type === "ready") { console.log("SOCKET | received ready from master"); this._state = "connected"; this._sessionToken = data.session_token; @@ -449,10 +445,12 @@ class VirtualConnector extends events.EventEmitter { /** * Send a message to the other end of the connector * - * @returns the sequence number of the message sent + * @param {string} type - Message type to send. + * @param {Object} data - Data to send with message. + * @returns {number} the sequence number of the message sent */ send(type, data) { - this.other.emit('message', { seq: this._seq, type, data }); + this.other.emit("message", { seq: this._seq, type, data }); return this._seq++; } } diff --git a/lib/link/index.js b/lib/link/index.js index e5c18c5f..2690e686 100644 --- a/lib/link/index.js +++ b/lib/link/index.js @@ -3,8 +3,9 @@ * @module lib/link * @author Hornwitser */ +"use strict"; module.exports = { - ...require('lib/link/link'), - ...require('lib/link/messages'), - ...require('lib/link/connectors'), + ...require("lib/link/link"), + ...require("lib/link/messages"), + ...require("lib/link/connectors"), }; diff --git a/lib/link/link.js b/lib/link/link.js index 67d2fc37..d05cfb53 100644 --- a/lib/link/link.js +++ b/lib/link/link.js @@ -30,17 +30,17 @@ class Link { this._validators = new Map(); // Process messages received by the connector - connector.on('message', payload => { + connector.on("message", payload => { try { this.processMessage(payload); } catch (err) { if (err instanceof errors.InvalidMessage) { - console.error(`Invalid message on ${this.source}-${this.target} link: ${err.message}`) + console.error(`Invalid message on ${this.source}-${this.target} link: ${err.message}`); if (err.errors) { console.error(err.errors); } } else { - this.connector.emit('error', err); + this.connector.emit("error", err); } } }); @@ -72,6 +72,7 @@ class Link { * Validates and invokes the handler and/or waiters for a message that has * been received. An unhandled message is considered to be an error. * + * @param {Object} message - Message to process. * @throws {module:lib/errors.InvalidMessage} * if validation failed, is missing, the message is invalid, or no * handlers/waiters were invoked to handle the message. @@ -102,6 +103,7 @@ class Link { /** * Invoke the handler for this message if it exists * + * @param {Object} message - Message to invoke handlers on. * @returns {Boolean} true if a handler was invoked. */ _processHandler(message) { @@ -117,6 +119,7 @@ class Link { /** * Invokes the waiters for this message if any * + * @param {Object} message - Message to invoke waiters on. * @returns {Boolean} true if a waiter was triggered. */ _processWaiters(message) { @@ -196,6 +199,9 @@ class Link { * as any properties specified in data. For example specifying { foo: * 21 } as the data will cause it to wait until a message with the right * type and a foo property value of 21 in the data payload. + * + * @param {string} type - Message type to wait for. + * @param {Object} data - Properties to match in the message data. */ async waitFor(type, data) { if (!this._validators.has(type)) { @@ -243,4 +249,4 @@ class Link { module.exports = { Link, -} +}; diff --git a/lib/link/messages.js b/lib/link/messages.js index c157dc7e..fd0d0876 100644 --- a/lib/link/messages.js +++ b/lib/link/messages.js @@ -11,8 +11,8 @@ const errors = require("lib/errors"); class Message { /** * Name of the plugin this message belongs to, or null. - * @returns {?string} - **/ + * @returns {?string} - plugin name or null if not from a plugin. + */ get plugin() { if (this._plugin === undefined) { let index = this.type.indexOf(":"); @@ -67,8 +67,8 @@ class Request extends Message { this.permission = permission || null; this.forwardTo = forwardTo; - this.requestType = type + '_request'; - this.responseType = type + '_response'; + this.requestType = type + "_request"; + this.responseType = type + "_response"; if (permission === undefined && links.includes("control-master")) { throw new Error(`permission is required for ${this.type} request over control-master links`); @@ -77,10 +77,11 @@ class Request extends Message { throw new Error(`permission is not allowed on ${this.type} request as it is not over control-master link`); } - if (forwardTo === 'instance') { - requestProperties = Object.assign( - {"instance_id": { type: "integer" }}, requestProperties - ); + if (forwardTo === "instance") { + requestProperties = { + "instance_id": { type: "integer" }, + ...requestProperties, + }; } else if (forwardTo !== "master" && forwardTo !== null) { throw new Error(`Invalid forwardTo value ${forwardTo}`); @@ -122,7 +123,7 @@ class Request extends Message { }, ], }, - } + }, }); } @@ -158,7 +159,7 @@ class Request extends Message { // Use forwarder if handler is not present. if (!handler) { - if (this.forwardTo === 'instance') { + if (this.forwardTo === "instance") { handler = link.forwardRequestToInstance; } else if (this.forwardTo === "master") { handler = link.forwardRequestToMaster; @@ -185,7 +186,7 @@ class Request extends Message { response = {}; } - if (!this._responseValidator({ seq: 0, type: this.responseType, response})) { + if (!this._responseValidator({ seq: 0, type: this.responseType, response })) { console.error(this._responseValidator.errors); throw new Error(`Validation failed responding to ${this.requestType}`); } @@ -208,11 +209,13 @@ class Request extends Message { * Sends the given data over the link as the request payload and waits * for the response. * + * @param {module:lib/link.Link} link - Link to send request over. + * @param {Object} data - Data to send with the request. * @returns {object} response data */ async send(link, data = {}) { // XXX validate link target/source? - if (!this._requestValidator({ seq: 0, type: this.requestType, data})) { + if (!this._requestValidator({ seq: 0, type: this.requestType, data })) { console.error(this._requestValidator.errors); throw new Error(`Validation failed sending ${this.requestType}`); } @@ -226,7 +229,7 @@ class Request extends Message { } -let messages = {} +let messages = {}; // Connection requests let wsLinks = ["master-control", "control-master", "master-slave", "slave-master"]; @@ -256,8 +259,8 @@ messages.debugDumpWs = new Request({ // Management requests messages.listSlaves = new Request({ - type: 'list_slaves', - links: ['control-master'], + type: "list_slaves", + links: ["control-master"], permission: "core.slave.list", responseProperties: { "list": { @@ -278,8 +281,8 @@ messages.listSlaves = new Request({ }); messages.listInstances = new Request({ - type: 'list_instances', - links: ['control-master'], + type: "list_instances", + links: ["control-master"], permission: "core.instance.list", responseProperties: { "list": { @@ -324,8 +327,8 @@ messages.createSlaveConfig = new Request({ }); messages.setInstanceOutputSubscriptions = new Request({ - type: 'set_instance_output_subscriptions', - links: ['control-master'], + type: "set_instance_output_subscriptions", + links: ["control-master"], permission: "core.instance.follow_log", requestProperties: { "instance_ids": { @@ -336,8 +339,8 @@ messages.setInstanceOutputSubscriptions = new Request({ }); messages.createInstance = new Request({ - type: 'create_instance', - links: ['control-master'], + type: "create_instance", + links: ["control-master"], permission: "core.instance.create", requestProperties: { "serialized_config": { type: "object" }, @@ -380,8 +383,8 @@ messages.setInstanceConfigProp = new Request({ }); messages.assignInstanceCommand = new Request({ - type: 'assign_instance_command', - links: ['control-master'], + type: "assign_instance_command", + links: ["control-master"], permission: "core.instance.assign", requestProperties: { "instance_id": { type: "integer" }, @@ -390,29 +393,29 @@ messages.assignInstanceCommand = new Request({ }); messages.assignInstance = new Request({ - type: 'assign_instance', - links: ['master-slave'], + type: "assign_instance", + links: ["master-slave"], requestProperties: { "serialized_config": { type: "object" }, }, - forwardTo: 'instance', -}) + forwardTo: "instance", +}); messages.startInstance = new Request({ - type: 'start_instance', - links: ['control-master', 'master-slave', 'slave-instance'], + type: "start_instance", + links: ["control-master", "master-slave", "slave-instance"], permission: "core.instance.start", requestProperties: { "save": { type: ["string", "null"] }, }, - forwardTo: 'instance', + forwardTo: "instance", }); messages.createSave = new Request({ - type: 'create_save', - links: ['control-master', 'master-slave', 'slave-instance'], + type: "create_save", + links: ["control-master", "master-slave", "slave-instance"], permission: "core.instance.create_save", - forwardTo: 'instance', + forwardTo: "instance", }); messages.exportData = new Request({ @@ -423,30 +426,30 @@ messages.exportData = new Request({ }); messages.stopInstance = new Request({ - type: 'stop_instance', - links: ['control-master', 'master-slave', 'slave-instance'], + type: "stop_instance", + links: ["control-master", "master-slave", "slave-instance"], permission: "core.instance.stop", - forwardTo: 'instance', + forwardTo: "instance", }); messages.deleteInstance = new Request({ - type: 'delete_instance', - links: ['control-master', 'master-slave'], + type: "delete_instance", + links: ["control-master", "master-slave"], permission: "core.instance.delete", - forwardTo: 'instance', + forwardTo: "instance", }); messages.sendRcon = new Request({ - type: 'send_rcon', - links: ['control-master', 'master-slave', 'slave-instance'], + type: "send_rcon", + links: ["control-master", "master-slave", "slave-instance"], permission: "core.instance.send_rcon", - forwardTo: 'instance', + forwardTo: "instance", requestProperties: { "command": { type: "string" }, }, responseProperties: { "result": { type: "string" }, - } + }, }); messages.listPermissions = new Request({ @@ -501,7 +504,7 @@ messages.createRole = new Request({ }, responseProperties: { "id": { type: "integer" }, - } + }, }); messages.updateRole = new Request({ @@ -583,8 +586,8 @@ messages.deleteUser = new Request({ }); messages.getMetrics = new Request({ - type: 'statistics_exporter:get_metrics', - links: ['master-slave', 'slave-instance'], + type: "statistics_exporter:get_metrics", + links: ["master-slave", "slave-instance"], responseProperties: { "results": { type: "array" }, }, @@ -632,20 +635,21 @@ class Event extends Message { this.forwardTo = forwardTo; this.broadcastTo = broadcastTo; - if (forwardTo === 'instance') { - eventProperties = Object.assign( - {"instance_id": { type: "integer" }}, eventProperties - ); + if (forwardTo === "instance") { + eventProperties = { + "instance_id": { type: "integer" }, + ...eventProperties, + }; - } else if (forwardTo !== 'master' && forwardTo !== null) { + } else if (forwardTo !== "master" && forwardTo !== null) { throw new Error(`Invalid forwardTo value ${forwardTo}`); } - if (broadcastTo !== 'instance' && broadcastTo !== null) { + if (broadcastTo !== "instance" && broadcastTo !== null) { throw new Error(`Invalid broadcastTo value ${broadcastTo}`); } - this.eventType = type + '_event'; + this.eventType = type + "_event"; this._eventValidator = schema.compile({ $schema: "http://json-schema.org/draft-07/schema#", properties: { @@ -685,17 +689,17 @@ class Event extends Message { if (this.links.includes(`${link.target}-${link.source}`)) { // Use forwarder if handler is not present. if (!handler) { - if (this.forwardTo === 'instance') { + if (this.forwardTo === "instance") { handler = link.forwardEventToInstance; - } else if (this.forwardTo === 'master') { + } else if (this.forwardTo === "master") { handler = link.forwardEventToMaster; } } if ( - this.broadcastTo === 'instance' + this.broadcastTo === "instance" && [ - 'instance-slave', 'slave-master', 'control-master', 'master-slave' + "instance-slave", "slave-master", "control-master", "master-slave", ].includes(`${link.target}-${link.source}`) ) { if (!handler) { @@ -705,7 +709,7 @@ class Event extends Message { handler = async (message, event) => { await link.broadcastEventToInstance(message, event); await originalHandler.call(link, message, event); - } + }; } } @@ -726,9 +730,12 @@ class Event extends Message { * Send event over the given link * * Sends the given event data over the link. + * + * @param {module:lib/link.Link} link - Link to send event over. + * @param {Object} data - Data to send with the event. */ send(link, data = {}) { - if (!this._eventValidator({ seq: 0, type: this.eventType, data})) { + if (!this._eventValidator({ seq: 0, type: this.eventType, data })) { console.error(this._eventValidator.errors); throw new Error(`Validation failed sending ${this.eventType}`); } @@ -754,7 +761,7 @@ messages.instanceInitialized = new Event({ "plugins": { type: "object", additionalProperties: { type: "string" }, - } + }, }, }); messages.instanceStarted = new Event({ @@ -765,7 +772,7 @@ messages.instanceStarted = new Event({ }, }); messages.instanceStopped = new Event({ - type: 'instance_stopped', + type: "instance_stopped", links: ["instance-slave", "slave-master"], eventProperties: { "instance_id": { type: "integer" }, @@ -773,8 +780,8 @@ messages.instanceStopped = new Event({ }); messages.updateInstances = new Event({ - type: 'update_instances', - links: ['slave-master'], + type: "update_instances", + links: ["slave-master"], eventProperties: { "instances": { type: "array", @@ -792,9 +799,9 @@ messages.updateInstances = new Event({ }); messages.instanceOutput = new Event({ - type: 'instance_output', - links: ['instance-slave', 'slave-master', 'master-control'], - forwardTo: 'master', + type: "instance_output", + links: ["instance-slave", "slave-master", "master-control"], + forwardTo: "master", eventProperties: { "instance_id": { type: "integer" }, "output": { @@ -843,11 +850,12 @@ messages.playerEvent = new Event({ * link instance as the concatenation of the name of the message, the * name of message class, and 'Handler'. * + * @param {module:lib/link.Link} link - Link to attach messages to. * @memberof module:lib/link */ function attachAllMessages(link) { for (let [name, message] of Object.entries(messages)) { - message.attach(link, link[name + message.constructor.name + 'Handler']); + message.attach(link, link[name + message.constructor.name + "Handler"]); } } @@ -860,4 +868,4 @@ module.exports = { attachAllMessages, messages, -} +}; diff --git a/lib/luaTools.js b/lib/luaTools.js index 7566e03e..48ad79dd 100644 --- a/lib/luaTools.js +++ b/lib/luaTools.js @@ -2,9 +2,13 @@ * Utilities for dealing with Lua * @module */ +"use strict"; /** * Escapes a string for inclusion into a lua string + * + * @param {string} content - String to escape. + * @returns {string} Escaped string. */ function escapeString(content) { return content @@ -19,4 +23,4 @@ function escapeString(content) { module.exports = { escapeString, -} +}; diff --git a/lib/manager/modManager.js b/lib/manager/modManager.js index 80e3b140..19fb28ac 100644 --- a/lib/manager/modManager.js +++ b/lib/manager/modManager.js @@ -1,41 +1,45 @@ +// This file will be rewritten from scratch +/* eslint-disable */ /** Provides tools to manage mods in a clusterio install @module */ -const asTable = require("as-table").configure({delimiter: ' | '}); +"use strict"; +const asTable = require("as-table").configure({delimiter: " | "}); const moment = require("moment"); const syncRequest = require("sync-request"); const fs = require("fs-extra"); -const https = require('follow-redirects').https; +const https = require("follow-redirects").https; const factorioAPI = require("node-factorio-api"); const sLog = require("single-line-log").stdout; -const query = require('cli-interact').getYesNo; +const query = require("cli-interact").getYesNo; const path = require("path"); const fileOps = require("lib/fileOps"); function ellipse(str, max){ - return str.length > (max - 3) ? str.substring(0,max-3) + '...' : str; + return str.length > (max - 3) ? str.substring(0,max-3) + "..." : str; } // https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript function formatBytes(size, decimals) { - if (bytes === 0) return '0 Bytes'; + if (bytes === 0) return "0 Bytes"; const k = 1024; const dm = decimals < 0 ? 0 : decimals; - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; } /** Lists mods for an instance as a table. Warning, handles printing internally. +@param {Object} config - Obsolete config @param {Instance} instance - Instance object, see slave.js @example @@ -52,7 +56,7 @@ async function listMods(config, instance) { factorioAPI.authenticate({ username: config.username, token: config.token, - require_ownership: false + require_ownership: false, }).then(token => { console.log("* Currently installed:"); factorioAPI.readModZips().then(list => { @@ -63,14 +67,14 @@ async function listMods(config, instance) { date:mod.date, factorio_ver:mod.factorio_version, author:mod.author, - } + }; }))); }); // Had issues earlier due to bug in node-factorio-api, see https://github.com/Danacus/node-factorio-api/issues/2 // bug is resolved, thanks to @Danacus and @brotherpot factorioAPI.getModsFromSaves().then(list => { console.log("* Mods used in save:"); - console.log(asTable(list[0].mods)) + console.log(asTable(list[0].mods)); }); }).catch(reason => { console.log("Failed to authenticate to factorio.com! Please check your connection and the username+token used for authentication in config.json"); @@ -78,14 +82,14 @@ async function listMods(config, instance) { }); } else { // list shared mods in /sharedMods/ - factorioAPI.init(false, 'sharedMods/', '', instance.server.version); + factorioAPI.init(false, "sharedMods/", "", instance.server.version); factorioAPI.authenticate({ username: config.username, token: config.token, - require_ownership: false + require_ownership: false, }).then(() => { - console.log("\r\n") - console.log("* Mods shared by all instances:") + console.log("\r\n"); + console.log("* Mods shared by all instances:"); factorioAPI.getInstalledMods().then(mods => { console.log(asTable(mods)); resolve(); @@ -108,6 +112,7 @@ async function listMods(config, instance) { Searches mods.factorio.com for mods matching a search term and prints the results to console as a neat looking table +@param {Object} config - Obsolete config @param {string} searchTerm What to search for */ async function findMods(config, searchTerm) { @@ -116,7 +121,7 @@ async function findMods(config, searchTerm) { factorioAPI.authenticate({ username: config.username, token: config.token, - require_ownership: false + require_ownership: false, }).then(token => { factorioAPI.searchMods(searchTerm).then(mods => { let result = asTable(mods.reverse().map(mod => { @@ -128,7 +133,7 @@ async function findMods(config, searchTerm) { latest_version: mod.latest_release.version, owner:mod.owner, //license: mod.license_name, - } + }; })); resolve(result); // console.log(result); @@ -154,7 +159,7 @@ async function addMod(config, term, instance) { factorioAPI.authenticate({ username: config.username, token: config.token, - require_ownership: false + require_ownership: false, }).then(() => { factorioAPI.searchMods(term.split("/")[term.split("/").length-1]).then(body => { console.log("Im gonna download: "+body[0].name + " in version: " + body[0].latest_release.version); @@ -168,7 +173,7 @@ async function addMod(config, term, instance) { // treat it as a github releases download let apiURL = "https://api.github.com/repos/" + term.split("/")[3] + "/" + term.split("/")[4] + "/releases"; // https://api.github.com/repos/Danielv123/factorioClusterioMod/releases - let res = syncRequest('GET', apiURL, {"headers":{"User-Agent":"factorioClusterio"}}); + let res = syncRequest("GET", apiURL, {"headers":{"User-Agent":"factorioClusterio"}}); let url = JSON.parse(res.getBody())[0].assets[0].browser_download_url; let name = JSON.parse(res.getBody())[0].assets[0].name; if (url) { @@ -177,7 +182,7 @@ async function addMod(config, term, instance) { let bytesDownloaded = 0; let downloadStartTime = Date.now(); https.get(url, function(response) { - let bytesToDownload = response.headers['content-length']; + let bytesToDownload = response.headers["content-length"]; response.pipe(file); response.on("data", chunk => { bytesDownloaded += chunk.length; @@ -188,7 +193,7 @@ async function addMod(config, term, instance) { "about "+timeToComplete+" seconds remaining ("+formatBytes(bytesPerSecond)+"/s)"); }).on("end", x => { sLog.clear(); - console.log(" Downloaded in "+moment.utc(moment.duration(Date.now()-downloadStartTime).as('milliseconds')).format('mm:ss')); // because sLog.clear() doesn't seem to do the job of ending the line properly + console.log(" Downloaded in "+moment.utc(moment.duration(Date.now()-downloadStartTime).as("milliseconds")).format("mm:ss")); // because sLog.clear() doesn't seem to do the job of ending the line properly resolve(); }); }); @@ -199,10 +204,10 @@ async function addMod(config, term, instance) { factorioAPI.authenticate({ username: config.username, token: config.token, - require_ownership: false + require_ownership: false, }).then(() => { factorioAPI.searchMods(term).then(body => { - console.log(body) + console.log(body); console.log("Im gonna download: "+body[0].name + " in version: " + body[0].latest_release.version); factorioAPI.downloadMod( {name: body[0].name, version: body[0].latest_release.version}).then(()=>{ console.log("Mod downloaded"); @@ -242,7 +247,7 @@ async function removeMod(config, term, instance, force = false) { fs.unlinkSync(path.join(modDirectory, modsMatchingFilter[i])); console.log("Deleted "+modsMatchingFilter[i]); } - } else console.log("Did not delete mods.") + } else console.log("Did not delete mods."); } resolve(); } else { @@ -261,10 +266,10 @@ the latest release, saving it to /sharedMods function downloadFromGitURL(url) { console.log("Downloading mod from "+url); // get JSON data about releases - let res = syncRequest('GET', url, {"headers":{"User-Agent":"factorioClusterio"}}); + let res = syncRequest("GET", url, {"headers":{"User-Agent":"factorioClusterio"}}); let downloadUrl = JSON.parse(res.getBody())[0].assets[0].browser_download_url; let name = JSON.parse(res.getBody())[0].assets[0].name; - console.log(JSON.parse(res.getBody())) + console.log(JSON.parse(res.getBody())); if (downloadUrl) { console.log(downloadUrl); let file = fs.createWriteStream("sharedMods/"+name); @@ -291,4 +296,4 @@ module.exports = { addMod, removeMod, downloadFromGitURL, -} +}; diff --git a/lib/npmPostinstall.js b/lib/npmPostinstall.js index ad3cbe83..cc23a327 100644 --- a/lib/npmPostinstall.js +++ b/lib/npmPostinstall.js @@ -1,6 +1,7 @@ -var http = require('http'); -var https = require('https'); -var fs = require('fs-extra'); +"use strict"; +var http = require("http"); +var https = require("https"); +var fs = require("fs-extra"); function makeDirectory(path){ @@ -25,7 +26,7 @@ function download(url, file, callback){ // this might be an invalid path! fs.stat(pathArray.join("/"), (err, stat) => { if(err){ - makeDirectory(pathArray.join("/")).then(()=>{ + makeDirectory(pathArray.join("/")).then(() => { doDownload(url, file, callback); }); } else { @@ -76,12 +77,12 @@ let downloadList = [ url: "https://fonts.googleapis.com/css?family=Roboto", file: "static/external/css/Roboto.css", }, -] +]; // initiate folder creations and downloads function processMkdirRecursive(callback){ let item = directories.shift(); - makeDirectory(item).then(()=>{ + makeDirectory(item).then(() => { if(directories.length == 0){ if(callback) callback(); } else { @@ -91,7 +92,7 @@ function processMkdirRecursive(callback){ } function processDownloadRecursive(callback){ let item = downloadList.shift(); - download(item.url, item.file, item.callback).then(()=>{ + download(item.url, item.file, item.callback).then(() => { if(downloadList.length == 0){ if(callback) callback(); } else { diff --git a/lib/objectOps.js b/lib/objectOps.js index 23053af3..1fe093cc 100644 --- a/lib/objectOps.js +++ b/lib/objectOps.js @@ -1,37 +1,39 @@ /** -Helper functions related to objects -@module -*/ + * Helper functions related to objects + * @module + */ +"use strict"; /** -Deep clones an object, is actually just JSON.parse(JSON.stringify()) but looks cleaner. -@param {object} obj to clone -@returns {object} - -@example -a = {"thingy":"with a value"}; -b = a; -a == b; // true -c = objectOps.deepclone(a); -a == c; // false -*/ + * Deep clones an object, is actually just JSON.parse(JSON.stringify()) but looks cleaner. + * @param {object} obj to clone + * @returns {object} The cloned object. + * + * @example + * a = {"thingy":"with a value"}; + * b = a; + * a == b; // true + * c = objectOps.deepclone(a); + * a == c; // false + */ module.exports.deepclone = function(obj){ return JSON.parse(JSON.stringify(obj)); -} -/** -Checks if a string is valid JSON using try/catch. If passed an object it will always return true. -@param {string} string to verify -@returns {boolean} +}; -@example -objectOps.isJSON("99 bottles of beer"); // false -objectOps.isJSON("99"); // true -objectOps.isJSON({"thingy":"that is an object"}); // true -objectOps.isJSON(JSON.stringify({"thingy":"that is an object"})); // true -*/ +/** + * Checks if a string is valid JSON using try/catch. If passed an object it will always return true. + * @param {string} string to verify + * @returns {boolean} true if valid JSON. + * + * @example + * objectOps.isJSON("99 bottles of beer"); // false + * objectOps.isJSON("99"); // true + * objectOps.isJSON({"thingy":"that is an object"}); // true + * objectOps.isJSON(JSON.stringify({"thingy":"that is an object"})); // true + */ module.exports.isJSON = function(string){ let stringIsJson = false; - let x + let x; try { x = JSON.parse(string); } catch (e){ @@ -41,4 +43,4 @@ module.exports.isJSON = function(string){ stringIsJson = true; } return stringIsJson; -} +}; diff --git a/lib/plugin.js b/lib/plugin.js index 20f8fed3..867e4048 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -9,6 +9,11 @@ const path = require("path"); const errors = require("lib/errors"); +/** + * Conceptual base for master and instance plugins. + * @typedef {module:lib/plugin.BaseMasterPlugin|module:lib/plugin.BaseInstancePlugin} module:lib/plugin.BasePlugin + */ + /** * Base class for instance plugins * @static @@ -51,7 +56,7 @@ class BaseInstancePlugin { * @param {module:lib/config.ConfigGroup} group - * The group who's field got changed on. * @param {string} field - Name of the field that changed. - * @param prev - The previous value of the field. + * @param {*} prev - The previous value of the field. */ async onInstanceConfigFieldChanged(group, field, prev) { } @@ -68,7 +73,7 @@ class BaseInstancePlugin { * the Gauges/Counters/etc in onMetric and let Clusterio deal will * collecting the values. * - * @returns an async iterator of prometheus metric results or undefined. + * @returns {*} an async iterator of prometheus metric results or undefined. */ async onMetrics() { } @@ -99,16 +104,23 @@ class BaseInstancePlugin { * * Called when the Factorio server outputs a line on either stdout or * stderr. The output is a parsed form of the line and contains the - * following properties (those marked with ? are optional): + * following properties: * - * - source: Where the message came from, one of "stdout" and "stderr". - * - format: Timestamp format, one of "date", "seconds" and "none". - * - time?: Timestamp of the message. Not present if format is "none". - * - type: Type of message, one of "log", "action" and "generic". - * - level?: Log level for "log" type. i.e "Info" normally. - * - file?: File reported for "log" type. - * - action?: Kind of action for "action" type. i.e "CHAT" for chat. - * - message: Main content of the line. + * @param {Object} output - parsed server output. + * @param {string} output.source - + * Where the message came from, one of "stdout" and "stderr". + * @param {string} output.format - + * Timestamp format, one of "date", "seconds" and "none". + * @param {string=} output.time - + * Timestamp of the message. Not present if format is "none". + * @param {string} output.type - + * Type of message, one of "log", "action" and "generic". + * @param {string=} output.level - + * Log level for "log" type. i.e "Info" normally. + * @param {string=} output.file - File reported for "log" type. + * @param {string=} output.action - + * Kind of action for "action" type. i.e "CHAT" for chat. + * @param {string} output.message - Main content of the line. */ async onOutput(output) { } @@ -215,7 +227,7 @@ class BaseMasterPlugin { * the Gauges/Counters/etc in onMetric and let Clusterio deal will * collecting the values. * - * @returns an async iterator of prometheus metric results or undefined. + * @returns {*} an async iterator of prometheus metric results or undefined. */ async onMetrics() { } @@ -249,9 +261,9 @@ class BaseMasterPlugin { * * Invoked when the connection to the slave has been closed. * - * @param {string} event - one of connect, drop, and close * @param {module:master~SlaveConnection} connection - * The connection the event occured on. + * @param {string} event - one of connect, drop, and close */ onSlaveConnectionEvent(connection, event) { } @@ -311,6 +323,7 @@ class BaseMasterPlugin { * module. Once loaded the info modules will not be reloaded should this * function be called again. * + * @param {string} baseDir - posix relative path to plugin directory. * @returns {Map} mapping of plugin name to info module. * @static */ @@ -321,10 +334,10 @@ async function loadPluginInfos(baseDir) { try { // Note: Require path is relative to this module - pluginInfo = require(path.posix.join('..', baseDir, pluginDir, 'info')); + pluginInfo = require(path.posix.join("..", baseDir, pluginDir, "info")); } catch (err) { - if (err.code === 'MODULE_NOT_FOUND') { + if (err.code === "MODULE_NOT_FOUND") { continue; } else { throw new errors.PluginError(pluginDir, err); @@ -350,7 +363,8 @@ async function loadPluginInfos(baseDir) { * * @param {module:lib/link.Link} link - Link to attach handlers to. * @param {object} pluginInfo - Plugin info object. - * @param plugin - The instance of the plugin to use handlers from. + * @param {module:lib/plugin.BasePlugin} plugin - + * The instance of the plugin to use handlers from. */ function attachPluginMessages(link, pluginInfo, plugin) { let messageDefinitions = pluginInfo.messages || []; @@ -359,7 +373,7 @@ function attachPluginMessages(link, pluginInfo, plugin) { throw new Error(`Type of ${name} message must start with "${pluginInfo.name}:"`); } - let handler = name + messageFormat.constructor.name + 'Handler'; + let handler = name + messageFormat.constructor.name + "Handler"; if (plugin === null || !plugin[handler]) { messageFormat.attach(link); @@ -374,7 +388,7 @@ function attachPluginMessages(link, pluginInfo, plugin) { /** * Invokes the given hook on all plugins * - * @param {Map} plugins - * Mapping of plugin names to plugins to invoke the hook on. * @param {string} hook - Name of hook to invoke. * @param {...*} args - Arguments to pass on to the hook. diff --git a/lib/prometheus.js b/lib/prometheus.js index 8913f52a..b461d60d 100644 --- a/lib/prometheus.js +++ b/lib/prometheus.js @@ -1,10 +1,11 @@ +"use strict"; class Metric { constructor(type, name, help, labels = []) { - if (typeof name !== 'string') { + if (typeof name !== "string") { throw new Error("Expected name to be a string"); } - if (typeof help !== 'string') { + if (typeof help !== "string") { throw new Error("Expected help to be a string"); } @@ -46,7 +47,7 @@ function escapeLabelValue(value) { } function labelsToKey(labels, metricLabels) { - let items = [] + let items = []; if (labels.length === 1 && typeof labels[0] === "object") { labels = labels[0]; for (let name of metricLabels) { @@ -73,7 +74,7 @@ function labelsToKey(labels, metricLabels) { } if (labels.length > metricLabels.length) { - throw new Error(`Extra positional label`); + throw new Error("Extra positional label"); } for (let i=0; i < metricLabels.length; ++i) { @@ -86,14 +87,14 @@ function labelsToKey(labels, metricLabels) { } } - return items.join(','); + return items.join(","); } function keyToLabels(key) { let labels = new Map(); - if (key !== '') for (let pair of key.split(',')) { - let [name, value] = pair.split('=', 2); + if (key !== "") for (let pair of key.split(",")) { + let [name, value] = pair.split("=", 2); labels.set(name, value .slice(1, -1) .replace(/\\"/g, "\"") @@ -111,9 +112,9 @@ class ValueCollector extends Collector { let register = true; let callback = null; for (let [name, value] of Object.entries(options)) { - if (name === 'labels') { labels = value; } - else if (name === 'register') { register = value; } - else if (name === 'callback') { callback = value; } + if (name === "labels") { labels = value; } + else if (name === "register") { register = value; } + else if (name === "callback") { callback = value; } else { throw new Error(`Unrecognized option '${name}'`); } } @@ -151,7 +152,7 @@ class ValueCollector extends Collector { remove(...labels) { let key = labelsToKey(labels, this.metric.labels); - if (key === '') { + if (key === "") { throw new Error("labels cannot be empty"); } this._children.delete(key); @@ -166,11 +167,11 @@ class ValueCollector extends Collector { function removeMatchingLabels(mapping, labels) { for (let key of mapping.keys()) { let candidate = keyToLabels(key); - let hasLabels = Object.entries(labels).every(([name, value]) => { - return candidate.get(name) === value; - }); + let hasLabels = Object.entries(labels).every(([name, value]) => ( + candidate.get(name) === value + )); if (hasLabels) { - mapping.delete(key) + mapping.delete(key); } } } @@ -208,7 +209,7 @@ class CounterChild extends ValueCollectorChild { throw new Error("Expected value to be a positive number"); } - this._values.set(this._key, this._values.get(this._key) + value) + this._values.set(this._key, this._values.get(this._key) + value); } } @@ -224,19 +225,19 @@ class Counter extends ValueCollector { class GaugeChild extends ValueCollectorChild { inc(value = 1) { - this._values.set(this._key, this._values.get(this._key) + value) + this._values.set(this._key, this._values.get(this._key) + value); } dec(value = 1) { - this._values.set(this._key, this._values.get(this._key) - value) + this._values.set(this._key, this._values.get(this._key) - value); } set(value) { - this._values.set(this._key, value) + this._values.set(this._key, value); } setToCurrentTime() { - this._values.set(this._key, Date.now() / 1000) + this._values.set(this._key, Date.now() / 1000); } } @@ -309,9 +310,9 @@ function escapeHelp(help) { function formatValue(value) { if (value === Infinity) { - return '+Inf'; + return "+Inf"; } else if (value === -Infinity) { - return '-Inf'; + return "-Inf"; } else { return value.toString(); } @@ -323,14 +324,14 @@ async function* expositionLines(resultsIterator) { if (first) { first = false; } else { - yield `\n` + yield "\n"; } yield `# HELP ${result.metric.name} ${escapeHelp(result.metric.help)}\n`; yield `# TYPE ${result.metric.name} ${result.metric.type}\n`; for (let [key, value] of result.samples) { - if (key === '') { + if (key === "") { yield `${result.metric.name} ${formatValue(value)}\n`; } else { yield `${result.metric.name}{${key}} ${formatValue(value)}\n`; @@ -340,7 +341,7 @@ async function* expositionLines(resultsIterator) { } async function exposition(resultsIterator = defaultRegistry.collect()) { - let exposition = ''; + let exposition = ""; for await (let line of expositionLines(resultsIterator)) { exposition += line; } @@ -348,16 +349,16 @@ async function exposition(resultsIterator = defaultRegistry.collect()) { } // HTTP Content-Type for the exposition format that's implemented -exposition.contentType = 'text/plain; version=0.0.4'; +exposition.contentType = "text/plain; version=0.0.4"; function serializeResult(result, options = {}) { let addLabels = null; let metricName = result.metric.name; let metricHelp = result.metric.help; for (let [name, value] of Object.entries(options)) { - if (name === 'addLabels') { addLabels = value; } - else if (name === 'metricName') { metricName = value; } - else if (name === 'metricHelp') { metricHelp = value; } + if (name === "addLabels") { addLabels = value; } + else if (name === "metricName") { metricName = value; } + else if (name === "metricHelp") { metricHelp = value; } else { throw new Error(`Unrecognized option '${name}'`); } } @@ -370,7 +371,7 @@ function serializeResult(result, options = {}) { samples = new Map(); let key = labelsToKey([addLabels], [...Object.keys(addLabels)]); for (let [labels, value] of result.samples.entries()) { - if (labels === '') { samples.set(key, value); } + if (labels === "") { samples.set(key, value); } else { samples.set(`${labels},${key}`, value); } } } @@ -395,12 +396,12 @@ function deserializeResult(serializedResult) { serializedResult.metric.labels, ), samples: new Map(serializedResult.samples), - } + }; } let defaultCollectors = {}; defaultCollectors.processCpuSecondsTotal = new Gauge( - 'process_cpu_seconds_total', + "process_cpu_seconds_total", "Total user and system CPU time spent in seconds.", { callback: async function() { @@ -411,7 +412,7 @@ defaultCollectors.processCpuSecondsTotal = new Gauge( ); defaultCollectors.processResidentMemoryBytes = new Gauge( - 'process_resident_memory_bytes', + "process_resident_memory_bytes", "Resident memory size in bytes.", { callback: function() { @@ -421,7 +422,7 @@ defaultCollectors.processResidentMemoryBytes = new Gauge( ); defaultCollectors.processHeapBytes = new Gauge( - 'process_heap_bytes', + "process_heap_bytes", "Process heap size in bytes.", { callback: function() { @@ -431,7 +432,7 @@ defaultCollectors.processHeapBytes = new Gauge( ); defaultCollectors.processStartTimeSeconds = new Gauge( - 'process_start_time_seconds', + "process_start_time_seconds", "Start time of the process since unix epoch in seconds." ); defaultCollectors.processStartTimeSeconds.setToCurrentTime(); diff --git a/lib/schema.js b/lib/schema.js index 84fc6de0..66f038bd 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -2,7 +2,8 @@ * JSON schemas used for validating link messages * @module */ -const ajv = new (require('ajv'))({ +"use strict"; +const ajv = new (require("ajv"))({ verbose: true, format: "full", extendRefs: "fail", @@ -73,7 +74,7 @@ const serverHandshake = ajv.compile({ $schema: "http://json-schema.org/draft-07/schema#", title: "Server Handshake", description: "Message sent from the server to a client during the handshake", - allOf: [ messageSchema ], + allOf: [messageSchema], anyOf: [ { properties: { @@ -87,7 +88,7 @@ const serverHandshake = ajv.compile({ type: "object", additionalProperties: { type: "string" }, }, - } + }, }, }, }, @@ -136,7 +137,7 @@ const clientHandshake = ajv.compile({ $schema: "http://json-schema.org/draft-07/schema#", title: "Client Message", description: "Message sent from a client to the server", - allOf: [ messageSchema ], + allOf: [messageSchema], anyOf: [ { properties: { @@ -168,7 +169,7 @@ const clientHandshake = ajv.compile({ "token": { type: "string" }, "agent": { type: "string" }, "version": { type: "string" }, - } + }, }, }, }, diff --git a/lib/users.js b/lib/users.js index 4cc855fb..11ef3fde 100644 --- a/lib/users.js +++ b/lib/users.js @@ -53,7 +53,7 @@ function definePermission({ name, title, description, grantByDefault=false }) { } if (permissions.has(name)) { - throw new Error(`Permission '${name}' is already defined`) + throw new Error(`Permission '${name}' is already defined`); } permissions.set(name, new Permission(name, title, description, grantByDefault)); @@ -95,6 +95,7 @@ class Role { * * Ensures the role with id 0 exisits and has the core.admin permission. * + * @param {Map} roles - role storage to modify. * @returns {module:lib/permission.Role} the default admin role * @static */ @@ -115,7 +116,7 @@ function ensureDefaultAdminRole(roles) { * assigned by default. Note that this may not be the same role as the one * that is assigned to new users by default. * - * @returns {module:lib/permission.Role} the default player role + * @param {Map} roles - role storage to modify. * @static */ function ensureDefaultPlayerRole(roles) { @@ -156,7 +157,7 @@ class User { /** * Roles this user has - * @type {Set} */ this.roles = new Set(); if (roles) { @@ -186,6 +187,7 @@ class User { * Generate access token for this user * * @param {string} secret - Secret to sign token with. + * @returns {string} JWT access token for the user. */ createToken(secret) { return jwt.sign({ aud: "user", user: this.name }, secret); @@ -197,7 +199,7 @@ class User { * Sets the tokenValidAfter property to the current time, which causes * all currently issued tokens for the user to become invalid. */ - invalidateToken(secret) { + invalidateToken() { this.tokenValidAfter = Math.floor(Date.now() / 1000); } @@ -208,6 +210,7 @@ class User { * permission. If the permission is not granted for the user a * "Permission denied" error is thrown. * + * @param {string} permission - The permission to check for. * @throws {Error} if the user does noh have the given permission. */ checkPermission(permission) { diff --git a/master.js b/master.js index f8210c09..234bd8bc 100644 --- a/master.js +++ b/master.js @@ -16,11 +16,12 @@ // const updater = require("./updater"); // updater.update().then(console.log); +"use strict"; const deepmerge = require("deepmerge"); const path = require("path"); const fs = require("fs-extra"); -const jwt = require('jsonwebtoken'); -const crypto = require('crypto'); +const jwt = require("jsonwebtoken"); +const crypto = require("crypto"); const moment = require("moment"); const request = require("request"); const setBlocking = require("set-blocking"); @@ -49,30 +50,30 @@ const config = require("lib/config"); const users = require("lib/users"); const express = require("express"); -const compression = require('compression'); -const cookieParser = require('cookie-parser'); +const compression = require("compression"); +const cookieParser = require("cookie-parser"); // Required for express post requests const bodyParser = require("body-parser"); -const fileUpload = require('express-fileupload'); +const fileUpload = require("express-fileupload"); var app = express(); var httpServer; var httpsServer; app.use(cookieParser()); app.use(bodyParser.json({ - limit: '10mb', + limit: "10mb", })); app.use(bodyParser.urlencoded({ parameterLimit: 100000, - limit: '10mb', - extended: true + limit: "10mb", + extended: true, })); app.use(fileUpload()); app.use(compression()); // dynamic HTML generations with EJS -app.set('view engine', 'ejs'); -app.set('views', ['views', 'plugins']); +app.set("view engine", "ejs"); +app.set("views", ["views", "plugins"]); // give ejs access to some interesting information app.use(function(req, res, next){ @@ -88,12 +89,12 @@ app.use(function(req, res, next){ require("./routes")(app); // Set folder to serve static content from (the website) -app.use(express.static('static')); +app.use(express.static("static")); const endpointHitCounter = new prometheus.Counter( "clusterio_master_http_endpoint_hits_total", "How many requests a particular HTTP endpoint has gotten", - { labels: ['route'] } + { labels: ["route"] } ); const wsMessageCounter = new prometheus.Counter( @@ -126,11 +127,11 @@ const wsActiveSlavesGauge = new prometheus.Gauge( async function getMetrics(req, res, next) { endpointHitCounter.labels(req.route.path).inc(); - let results = [] + let results = []; let pluginResults = await plugin.invokeHook(masterPlugins, "onMetrics"); for (let metricIterator of pluginResults) { for await (let metric of metricIterator) { - results.push(metric) + results.push(metric); } } @@ -167,10 +168,10 @@ async function getMetrics(req, res, next) { let text = await prometheus.exposition(results); - res.set('Content-Type', prometheus.exposition.contentType); + res.set("Content-Type", prometheus.exposition.contentType); res.send(text); } -app.get('/metrics', (req, res, next) => getMetrics(req, res, next).catch(next)); +app.get("/metrics", (req, res, next) => getMetrics(req, res, next).catch(next)); function validateSlaveToken(req, res, next) { @@ -232,9 +233,9 @@ app.put("/api/upload-export", ); const masterConnectedClientsCount = new prometheus.Gauge( - 'clusterio_master_connected_clients_count', "How many clients are currently connected to this master server", + "clusterio_master_connected_clients_count", "How many clients are currently connected to this master server", { - labels: ['type'], callback: async function(gauge) { + labels: ["type"], callback: async function(gauge) { gauge.labels("slave").set(slaveConnections.size); gauge.labels("control").set(controlConnections.length); }, @@ -246,6 +247,10 @@ const db = {}; /** * Load Map from JSON file in the database directory. + * + * @param {string} databaseDirectory - Path to master database directory. + * @param {string} file - Name of file to load. + * @returns {Map} file loaded as a map. */ async function loadMap(databaseDirectory, file) { let databasePath = path.resolve(databaseDirectory, file); @@ -267,7 +272,7 @@ async function loadInstances(databaseDirectory, file) { } } catch (err) { - if (err.code !== 'ENOENT') { + if (err.code !== "ENOENT") { throw err; } } @@ -314,17 +319,21 @@ async function loadUsers(databaseDirectory, file) { */ function createUser(name) { if (db.users.has(name)) { - throw new Error(`User '${name}' already exists`) + throw new Error(`User '${name}' already exists`); } let defaultRoleId = masterConfig.get("master.default_role_id"); - user = new users.User({ name, roles: [defaultRoleId] }, db.roles); + let user = new users.User({ name, roles: [defaultRoleId] }, db.roles); db.users.set(name, user); return user; } /** * Save Map to JSON file in the database directory. + * + * @param {string} databaseDirectory - Path to master database directory. + * @param {string} file - Name of file to save. + * @param {Map} map - Mapping to save into file. */ async function saveMap(databaseDirectory, file, map) { let databasePath = path.resolve(databaseDirectory, file); @@ -365,14 +374,14 @@ async function saveUsers(databaseDirectory, file) { * Innitiate shutdown of master server */ async function shutdown() { - console.log('Shutting down'); + console.log("Shutting down"); let exitStartTime = Date.now(); try { console.log("Saving configs"); await fs.outputFile(masterConfigPath, JSON.stringify(masterConfig.serialize(), null, 4)); - await saveMap(masterConfig.get('master.database_directory'), "slaves.json", db.slaves); - await saveInstances(masterConfig.get('master.database_directory'), "instances.json", db.instances); + await saveMap(masterConfig.get("master.database_directory"), "slaves.json", db.slaves); + await saveInstances(masterConfig.get("master.database_directory"), "instances.json", db.instances); await saveUsers(masterConfig.get("master.database_directory"), "users.json"); await plugin.invokeHook(masterPlugins, "onShutdown"); @@ -439,7 +448,7 @@ async function downloadPage(url) { app.get("/api/modmeta", async function(req, res) { endpointHitCounter.labels(req.route.path).inc(); res.header("Access-Control-Allow-Origin", "*"); - res.setHeader('Content-Type', 'application/json'); + res.setHeader("Content-Type", "application/json"); let modData = await downloadPage("https://mods.factorio.com/api/mods/" + req.query.modname); res.send(modData); }); @@ -452,7 +461,7 @@ app.get("/api/modmeta", async function(req, res) { */ class BaseConnection extends link.Link { constructor(target, connector) { - super('master', target, connector); + super("master", target, connector); link.attachAllMessages(this); for (let masterPlugin of masterPlugins.values()) { plugin.attachPluginMessages(this, masterPlugin.info, masterPlugin); @@ -465,7 +474,7 @@ class BaseConnection extends link.Link { throw new errors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`); } - let slaveId = instance.config.get('instance.assigned_slave'); + let slaveId = instance.config.get("instance.assigned_slave"); if (slaveId === null) { throw new errors.RequestError("Instance is not assigned to a slave"); } @@ -475,7 +484,7 @@ class BaseConnection extends link.Link { throw new errors.RequestError("Slave containing instance is not connected"); } if (request.plugin && !connection.plugins.has(request.plugin)) { - throw new errors.RequestError(`Slave containing instance does not have ${request.plugin} plugin`) + throw new errors.RequestError(`Slave containing instance does not have ${request.plugin} plugin`); } return await request.send(connection, message.data); @@ -485,7 +494,7 @@ class BaseConnection extends link.Link { let instance = db.instances.get(message.data.instance_id); if (!instance) { return; } - let slaveId = instance.config.get('instance.assigned_slave'); + let slaveId = instance.config.get("instance.assigned_slave"); if (slaveId === null) { return; } let connection = slaveConnections.get(slaveId); @@ -526,10 +535,10 @@ class BaseConnection extends link.Link { } } -let controlConnections = new Array(); +let controlConnections = []; class ControlConnection extends BaseConnection { constructor(registerData, connector, user) { - super('control', connector) + super("control", connector); this._agent = registerData.agent; this._version = registerData.version; @@ -551,7 +560,7 @@ class ControlConnection extends BaseConnection { this.ws_dumper = null; this.connector.on("connect", () => { - this.connector._socket.clusterio_ignore_dump = !!this.ws_dumper; + this.connector._socket.clusterio_ignore_dump = Boolean(this.ws_dumper); }); this.connector.on("close", () => { if (this.ws_dumper) { @@ -617,7 +626,7 @@ class ControlConnection extends BaseConnection { let instanceConfig = new config.InstanceConfig(); await instanceConfig.load(message.data.serialized_config); - let instanceId = instanceConfig.get("instance.id") + let instanceId = instanceConfig.get("instance.id"); if (db.instances.has(instanceId)) { throw new errors.RequestError(`Instance with ID ${instanceId} already exists`); } @@ -630,7 +639,7 @@ class ControlConnection extends BaseConnection { throw new errors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`); } - if (instance.config.get('instance.assigned_slave') !== null) { + if (instance.config.get("instance.assigned_slave") !== null) { await this.forwardRequestToInstance(message, request); } db.instances.delete(message.data.instance_id); @@ -644,7 +653,7 @@ class ControlConnection extends BaseConnection { return { serialized_config: instance.config.serialize(), - } + }; } async updateInstanceConfig(instance) { @@ -685,7 +694,7 @@ class ControlConnection extends BaseConnection { throw new errors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`); } - let {field, prop, value} = message.data; + let { field, prop, value } = message.data; instance.config.setProp(field, prop, value); await this.updateInstanceConfig(instance); } @@ -708,7 +717,7 @@ class ControlConnection extends BaseConnection { throw new errors.RequestError("Target slave is not connected to the master server"); } - instance.config.set("instance.assigned_slave", message.data.slave_id) + instance.config.set("instance.assigned_slave", message.data.slave_id); return await link.messages.assignInstance.send(connection, { instance_id: instance.config.get("instance.id"), @@ -750,7 +759,7 @@ class ControlConnection extends BaseConnection { // Start at 5 to leave space for future default roles let id = Math.max(5, lastId+1); - db.roles.set(id, new users.Role({ id, ...message.data})); + db.roles.set(id, new users.Role({ id, ...message.data })); return { id }; } @@ -836,7 +845,7 @@ class ControlConnection extends BaseConnection { } }; this.connector._socket.clusterio_ignore_dump = true; - debugEvents.on("message", this.ws_dumper) + debugEvents.on("message", this.ws_dumper); } } @@ -848,7 +857,7 @@ var slaveConnections = new Map(); */ class SlaveConnection extends BaseConnection { constructor(registerData, connector) { - super('slave', connector); + super("slave", connector); this._agent = registerData.agent; this._id = registerData.id; @@ -871,6 +880,7 @@ class SlaveConnection extends BaseConnection { }); for (let event of ["connect", "drop", "close"]) { + // eslint-disable-next-line no-loop-func this.connector.on(event, () => { for (let masterPlugin of masterPlugins.values()) { masterPlugin.onSlaveConnectionEvent(this, event); @@ -883,7 +893,7 @@ class SlaveConnection extends BaseConnection { let instance = db.instances.get(message.data.instance_id); let prev = instance.status; instance.status = "initialized"; - console.log(`Clusterio | Instance ${instance.config.get("instance.name")} Initialized`) + console.log(`Clusterio | Instance ${instance.config.get("instance.name")} Initialized`); await plugin.invokeHook(masterPlugins, "onInstanceStatusChanged", instance, prev); } @@ -891,7 +901,7 @@ class SlaveConnection extends BaseConnection { let instance = db.instances.get(message.data.instance_id); let prev = instance.status; instance.status = "running"; - console.log(`Clusterio | Instance ${instance.config.get("instance.name")} Started`) + console.log(`Clusterio | Instance ${instance.config.get("instance.name")} Started`); await plugin.invokeHook(masterPlugins, "onInstanceStatusChanged", instance, prev); } @@ -899,7 +909,7 @@ class SlaveConnection extends BaseConnection { let instance = db.instances.get(message.data.instance_id); let prev = instance.status; instance.status = "stopped"; - console.log(`Clusterio | Instance ${instance.config.get("instance.name")} Stopped`) + console.log(`Clusterio | Instance ${instance.config.get("instance.name")} Stopped`); await plugin.invokeHook(masterPlugins, "onInstanceStatusChanged", instance, prev); } @@ -919,7 +929,7 @@ class SlaveConnection extends BaseConnection { let instanceConfig = new config.InstanceConfig(); await instanceConfig.load(instance.serialized_config); - let masterInstance = db.instances.get(instanceConfig.get("instance.id")) + let masterInstance = db.instances.get(instanceConfig.get("instance.id")); if (masterInstance) { // Already have this instance, update state instead if (masterInstance.status !== instance.status) { @@ -951,7 +961,7 @@ class SlaveConnection extends BaseConnection { } async playerEventEventHandler(message) { - let {instance_id, name, type} = message.data; + let { instance_id, name, type } = message.data; let user = db.users.get(name); if (!user) { user = createUser(name); @@ -976,13 +986,13 @@ const wss = new WebSocket.Server({ /** * Returns true if value is a signed 32-bit integer * - * @param value - value to test. + * @param {number} value - value to test. * @returns {Boolean} * true if value is an integer between -231 and * 231-1. */ function isInteger(value) { - return value | 0 === value; + return (value | 0) === value; } @@ -1020,9 +1030,12 @@ class WebSocketServerConnector extends link.WebSocketBaseConnector { * Send ready over the socket * * Sends the ready message over the socket to initiate the session. + * + * @param {string} sessionToken - + * the session token to send to the client. */ ready(sessionToken) { - this._heartbeatInterval = masterConfig.get("master.heartbeat_interval") + this._heartbeatInterval = masterConfig.get("master.heartbeat_interval"); this._socket.send(JSON.stringify({ seq: null, type: "ready", @@ -1043,6 +1056,9 @@ class WebSocketServerConnector extends link.WebSocketBaseConnector { * * Terminates the current socket and contiunes the session over the * socket given from the message sequence given. + * + * @param {module:net.Socket} socket - New socket to continue on. + * @param {number} lastSeq - The last message the client received. */ continue(socket, lastSeq) { this._socket.terminate(); @@ -1053,7 +1069,7 @@ class WebSocketServerConnector extends link.WebSocketBaseConnector { this._timeoutId = null; } - this._heartbeatInterval = masterConfig.get("master.heartbeat_interval") + this._heartbeatInterval = masterConfig.get("master.heartbeat_interval"); this._socket.send(JSON.stringify({ seq: null, type: "continue", @@ -1078,7 +1094,7 @@ class WebSocketServerConnector extends link.WebSocketBaseConnector { clearTimeout(this._timeoutId); } - this._timeoutId = setTimeout(() => { this._timedOut() }, timeout * 1000); + this._timeoutId = setTimeout(() => { this._timedOut(); }, timeout * 1000); this._timeout = timeout; } @@ -1117,7 +1133,7 @@ class WebSocketServerConnector extends link.WebSocketBaseConnector { } else { this._state = "handshake"; this.emit("drop"); - this._timeoutId = setTimeout(() => { this._timedOut() }, this._timeout * 1000); + this._timeoutId = setTimeout(() => { this._timedOut(); }, this._timeout * 1000); } this.stopHeartbeat(); @@ -1163,6 +1179,9 @@ class WebSocketServerConnector extends link.WebSocketBaseConnector { * Close the connection with the given reason. * * Sends a close frame and disconnects the connector. + * + * @param {number} code - WebSocket close code. + * @param {string} reason - WebSocket close reason. */ async close(code, reason) { if (this._state === "closed") { @@ -1172,7 +1191,7 @@ class WebSocketServerConnector extends link.WebSocketBaseConnector { this.stopHeartbeat(); this._state = "closing"; this._socket.close(code, reason); - await events.once(this, "close") + await events.once(this, "close"); } } @@ -1202,7 +1221,7 @@ wss.on("connection", function (socket, req) { // Start connection handshake. socket.send(JSON.stringify({ seq: null, type: "hello", data: { version, - plugins: pluginList + plugins: pluginList, }})); function attachHandler() { @@ -1283,7 +1302,7 @@ async function handleHandshake(message, socket, req, attachHandler) { } connector.continue(socket, data.last_seq); - return + return; } if (stopAcceptingNewSessions) { @@ -1332,7 +1351,7 @@ async function handleHandshake(message, socket, req, attachHandler) { } let sessionId = nextSessionId++; - let sessionToken = jwt.sign({ aud: masterSession, sid: sessionId, }, masterConfig.get("master.auth_secret")); + let sessionToken = jwt.sign({ aud: masterSession, sid: sessionId }, masterConfig.get("master.auth_secret")); let connector = new WebSocketServerConnector(socket, sessionId); activeConnectors.set(sessionId, connector); @@ -1365,7 +1384,7 @@ async function pluginManagement(pluginInfos) { async function loadPlugins(pluginInfos) { let plugins = new Map(); - for (pluginInfo of pluginInfos) { + for (let pluginInfo of pluginInfos) { if (!masterConfig.group(pluginInfo.name).get("enabled")) { continue; } @@ -1386,7 +1405,7 @@ async function loadPlugins(pluginInfos) { plugins.set(pluginInfo.name, masterPlugin); } catch (err) { - throw new errors.PluginError(pluginInfo.name, err) + throw new errors.PluginError(pluginInfo.name, err); } console.log(`Clusterio | Loaded plugin ${pluginInfo.name} in ${Date.now() - pluginLoadStarted}ms`); @@ -1398,6 +1417,10 @@ async function loadPlugins(pluginInfos) { * Calls listen on server capturing any errors that occurs * binding to the port. Also adds handler for WebSocket * upgrade event. + * + * @param {module:net.Server} server - Server to start the listening on. + * @param {*} args - Arguments to the .listen() call on the server. + * @returns {Promise} promise that resolves the server is listening. */ function listen(server, ...args) { return new Promise((resolve, reject) => { @@ -1407,7 +1430,7 @@ function listen(server, ...args) { // For reasons that defy common sense, the connection event has // to be emitted explictly when using noServer. wss.handleUpgrade(req, socket, head, (ws) => { - wss.emit('connection', ws, req); + wss.emit("connection", ws, req); }); }); @@ -1417,9 +1440,9 @@ function listen(server, ...args) { )); } - server.once('error', wrapError); + server.once("error", wrapError); server.listen(...args, () => { - server.off('error', wrapError); + server.off("error", wrapError); resolve(); }); }); @@ -1431,9 +1454,11 @@ function _setConfig(config) { /** * Returns the URL needed to connect to the master server. + * + * @returns {string} master URL. */ function getMasterUrl() { - let url = masterConfig.get("master.external_address") + let url = masterConfig.get("master.external_address"); if (!url) { if (masterConfig.get("master.https_port")) { url = `https://localhost:${masterConfig.get("master.https_port")}/`; @@ -1450,7 +1475,7 @@ async function startServer() { process.title = "clusterioMaster"; // add better stack traces on promise rejection - process.on('unhandledRejection', r => console.log(r)); + process.on("unhandledRejection", r => console.log(r)); // argument parsing let args = yargs @@ -1472,7 +1497,7 @@ async function startServer() { nargs: 1, default: "config-control.json", }); }) - .demandCommand(1, "You need to specify a command to run") + .demandCommand(1, "You need to specify a command to run"); }) .command("run", "Run master server") .demandCommand(1, "You need to specify a command to run") @@ -1481,7 +1506,7 @@ async function startServer() { ; console.log("Loading Plugin info"); - let pluginInfos = await plugin.loadPluginInfos("plugins") + let pluginInfos = await plugin.loadPluginInfos("plugins"); config.registerPluginConfigGroups(pluginInfos); config.finalizeConfigs(); @@ -1492,7 +1517,7 @@ async function startServer() { await masterConfig.load(JSON.parse(await fs.readFile(masterConfigPath))); } catch (err) { - if (err.code === 'ENOENT') { + if (err.code === "ENOENT") { console.log("Config not found, initializing new config"); await masterConfig.init(); @@ -1554,8 +1579,8 @@ async function startServer() { // If we get here the command was run - let secondSigint = false - process.on('SIGINT', () => { + let secondSigint = false; + process.on("SIGINT", () => { if (secondSigint) { console.log("Caught second interrupt, terminating immediately"); process.exit(1); @@ -1567,28 +1592,28 @@ async function startServer() { }); // terminal closed - process.on('SIGHUP', () => { + process.on("SIGHUP", () => { // No graceful cleanup, no warning out (stdout is likely closed.) // Don't close the terminal with the clusterio master in it. process.exit(1); }); - await fs.ensureDir(masterConfig.get('master.database_directory')); + await fs.ensureDir(masterConfig.get("master.database_directory")); - db.slaves = await loadMap(masterConfig.get('master.database_directory'), "slaves.json"); - db.instances = await loadInstances(masterConfig.get('master.database_directory'), "instances.json"); + db.slaves = await loadMap(masterConfig.get("master.database_directory"), "slaves.json"); + db.instances = await loadInstances(masterConfig.get("master.database_directory"), "instances.json"); await loadUsers(masterConfig.get("master.database_directory"), "users.json"); // Make sure we're actually going to listen on a port - let httpPort = masterConfig.get('master.http_port'); - let httpsPort = masterConfig.get('master.https_port'); + let httpPort = masterConfig.get("master.http_port"); + let httpsPort = masterConfig.get("master.https_port"); if (!httpPort && !httpsPort) { console.error("Error: at least one of http_port and https_port must be configured"); process.exit(1); } - let tls_cert = masterConfig.get('master.tls_certificate'); - let tls_key = masterConfig.get('master.tls_private_key'); + let tls_cert = masterConfig.get("master.tls_certificate"); + let tls_key = masterConfig.get("master.tls_private_key"); // Create a self signed certificate if the certificate files doesn't exist if (httpsPort && !await fs.exists(tls_cert) && !await fs.exists(tls_key)) { await generateSSLcert({ @@ -1623,8 +1648,8 @@ async function startServer() { httpsServer = require("https").createServer({ key: privateKey, - cert: certificate - }, app) + cert: certificate, + }, app); await listen(httpsServer, httpsPort); console.log("Listening for HTTPS on port %s...", httpsServer.address().port); } @@ -1641,7 +1666,7 @@ module.exports = { _ControlConnection: ControlConnection, _slaveConnections: slaveConnections, _SlaveConnection: SlaveConnection, -} +}; if (module === require.main) { console.warn(` diff --git a/package.json b/package.json index fc2842fb..ee0d7b5c 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "cover": "nyc npm test", "fast-cover": "FAST_TEST=y nyc npm test", "ci-cover": "nyc --reporter=lcovonly npm run-script test", + "lint": "eslint \"*.js\" lib plugins test", "docs": "jsdoc --configure docs/jsdoc.json", "install": "node lib/npmPostinstall", "bundle-dependencies": "bundle-dependencies" @@ -70,6 +71,7 @@ "yargs": "^14.2.0" }, "devDependencies": { + "eslint": "^7.2.0", "html5-validator": "^1.2.1", "jsdoc": "^3.6.3", "klaw": "^3.0.0", diff --git a/plugins/global_chat/info.js b/plugins/global_chat/info.js index 228bd554..69764974 100644 --- a/plugins/global_chat/info.js +++ b/plugins/global_chat/info.js @@ -1,3 +1,4 @@ +"use strict"; let link = require("lib/link"); let plugin = require("lib/plugin"); @@ -10,14 +11,14 @@ module.exports = { messages: { chat: new link.Event({ - type: 'global_chat:chat', - links: ['instance-slave', 'slave-master', 'master-slave', 'slave-instance'], - forwardTo: 'master', - broadcastTo: 'instance', + type: "global_chat:chat", + links: ["instance-slave", "slave-master", "master-slave", "slave-instance"], + forwardTo: "master", + broadcastTo: "instance", eventProperties: { "instance_name": { type: "string" }, "content": { type: "string" }, }, }), }, -} +}; diff --git a/plugins/global_chat/instance.js b/plugins/global_chat/instance.js index cf6cfbee..5cb2dadb 100644 --- a/plugins/global_chat/instance.js +++ b/plugins/global_chat/instance.js @@ -1,12 +1,16 @@ /** * @module */ +"use strict"; const plugin = require("lib/plugin"); const luaTools = require("lib/luaTools"); /** * Removes gps and train tags from messags + * + * @param {string} content - string to strip tags from. + * @returns {string} stripped string. */ function removeTags(content) { return content.replace(/(\[gps=-?\d+,-?\d+\]|\[train=\d+\])/g, ""); @@ -57,4 +61,4 @@ module.exports = { // For testing only _removeTags: removeTags, -} +}; diff --git a/plugins/global_chat/test/plugin.js b/plugins/global_chat/test/plugin.js index 1652282e..7576a9b6 100644 --- a/plugins/global_chat/test/plugin.js +++ b/plugins/global_chat/test/plugin.js @@ -1,9 +1,10 @@ -const assert = require('assert').strict; +"use strict"; +const assert = require("assert").strict; -const mock = require('../../../test/mock'); -const lines = require('../../../test/lib/factorio/lines'); -const instance = require('../instance'); -const info = require('../info'); +const mock = require("../../../test/mock"); +const lines = require("../../../test/lib/factorio/lines"); +const instance = require("../instance"); +const info = require("../info"); describe("global_chat plugin", function() { @@ -62,6 +63,6 @@ describe("global_chat plugin", function() { } assert(count > 0, "no lines were tested"); }); - }) + }); }); }); diff --git a/plugins/research_sync/info.js b/plugins/research_sync/info.js index 2e184014..c9d85316 100644 --- a/plugins/research_sync/info.js +++ b/plugins/research_sync/info.js @@ -1,3 +1,4 @@ +"use strict"; const link = require("lib/link"); @@ -13,7 +14,7 @@ const technologies = { { type: "integer" }, { type: ["null", "number"] }, { type: "boolean" }, - ] + ], }, }; @@ -77,4 +78,4 @@ module.exports = { }, }), }, -} +}; diff --git a/plugins/research_sync/instance.js b/plugins/research_sync/instance.js index ffbeb3a0..d87b0b29 100644 --- a/plugins/research_sync/instance.js +++ b/plugins/research_sync/instance.js @@ -1,3 +1,4 @@ +"use strict"; const needle = require("needle"); const plugin = require("lib/plugin"); @@ -5,8 +6,8 @@ const luaTools = require("lib/luaTools"); function unexpectedError(err) { - console.log("Unexpected error in research_sync") - console.log("---------------------------------") + console.log("Unexpected error in research_sync"); + console.log("---------------------------------"); console.log(err); } @@ -78,4 +79,4 @@ class InstancePlugin extends plugin.BaseInstancePlugin { module.exports = { InstancePlugin, -} +}; diff --git a/plugins/research_sync/master.js b/plugins/research_sync/master.js index fa114503..a6f0cbd4 100644 --- a/plugins/research_sync/master.js +++ b/plugins/research_sync/master.js @@ -1,3 +1,4 @@ +"use strict"; const fs = require("fs-extra"); const path = require("path"); @@ -38,7 +39,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin { } broadcastProgress() { - let techs = [] + let techs = []; for (let name of this.progressToBroadcast) { let tech = this.technologies.get(name); if (tech.progress) { @@ -58,7 +59,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin { let tech = this.technologies.get(name); if (!tech) { tech = { level, progress: 0, researched: false }; - this.technologies.set(name, tech) + this.technologies.set(name, tech); // Ignore contribution to already researched technologies } else if (tech.level > level || tech.level === level && tech.researched) { @@ -96,7 +97,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin { let tech = this.technologies.get(name); if (!tech || tech.level <= level) { this.progressToBroadcast.delete(name); - this.technologies.set(name, { level, progress: null, researched: true }) + this.technologies.set(name, { level, progress: null, researched: true }); } } @@ -111,7 +112,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin { } for (let instanceTech of message.data.technologies) { - let [name, level, progress, researched] = instanceTech; + let [name, level, progress, researched] = instanceTech; let tech = this.technologies.get(name); if (!tech) { this.technologies.set(name, { level, progress, researched }); @@ -151,10 +152,10 @@ class MasterPlugin extends plugin.BaseMasterPlugin { let technologies = []; for (let [name, tech] of this.technologies) { - technologies.push([ name, tech.level, tech.progress, tech.researched ]) + technologies.push([name, tech.level, tech.progress, tech.researched]); } - return { technologies } + return { technologies }; } } @@ -183,5 +184,5 @@ async function saveTechnologies(masterConfig, technologies) { module.exports = { - MasterPlugin -} + MasterPlugin, +}; diff --git a/plugins/research_sync/test/missing.js b/plugins/research_sync/test/missing.js index c0b5b5ab..4cab8cce 100644 --- a/plugins/research_sync/test/missing.js +++ b/plugins/research_sync/test/missing.js @@ -1,5 +1,6 @@ // These are libraries that does not have any units test, they are // required here in order to track coverage for them. -require('../instance'); -require('../master'); +"use strict"; +require("../instance"); +require("../master"); diff --git a/plugins/statistics_exporter/info.js b/plugins/statistics_exporter/info.js index a1bf60da..ca83b9b5 100644 --- a/plugins/statistics_exporter/info.js +++ b/plugins/statistics_exporter/info.js @@ -1,3 +1,4 @@ +"use strict"; let link = require("lib/link"); module.exports = { @@ -6,4 +7,4 @@ module.exports = { description: "Exports statistics to Prometheus", version: "2.0.0-alpha", instanceEntrypoint: "instance", -} +}; diff --git a/plugins/statistics_exporter/instance.js b/plugins/statistics_exporter/instance.js index 4ca365ad..b76ae99b 100644 --- a/plugins/statistics_exporter/instance.js +++ b/plugins/statistics_exporter/instance.js @@ -1,28 +1,29 @@ -const plugin = require('lib/plugin'); -const { Gauge } = require('lib/prometheus'); +"use strict"; +const plugin = require("lib/plugin"); +const { Gauge } = require("lib/prometheus"); const instancePlayerCount = new Gauge( "clusterio_statistics_exporter_instance_player_count", "Amount of players connected to this cluster", - { labels: ['instance_id'] } + { labels: ["instance_id"] } ); const instanceGameTicksTotal = new Gauge( "clusterio_statistics_exporter_instance_game_ticks_total", "Game tick an instance has progressed to", - { labels: ['instance_id'] } + { labels: ["instance_id"] } ); const instanceForceFlowStatistics = new Gauge( "clusterio_statistics_exporter_instance_force_flow_statistics", "Items/fluids/enemies/buildings produced/built/killed by a force", - { labels: ['instance_id', 'force', 'statistic', 'direction', 'name'] }, + { labels: ["instance_id", "force", "statistic", "direction", "name"] }, ); const instanceGameFlowStatistics = new Gauge( "clusterio_statistics_exporter_instance_game_flow_statistics", "Pollution produced/consumed in the game", - { labels: ['instance_id', 'statistic', 'direction', 'name'] }, + { labels: ["instance_id", "statistic", "direction", "name"] }, ); class InstancePlugin extends plugin.BaseInstancePlugin { async onMetrics() { - let string = await this.instance.server.sendRcon('/sc statistics_exporter.export()'); + let string = await this.instance.server.sendRcon("/sc statistics_exporter.export()"); let stats; try { stats = JSON.parse(string); @@ -64,4 +65,4 @@ module.exports = { _instanceGameTicksTotal: instanceGameTicksTotal, _instanceForceFlowStatistics: instanceForceFlowStatistics, _instanceGameFlowStatistics: instanceGameFlowStatistics, -} +}; diff --git a/plugins/statistics_exporter/test/plugin.js b/plugins/statistics_exporter/test/plugin.js index 2112a441..52d18595 100644 --- a/plugins/statistics_exporter/test/plugin.js +++ b/plugins/statistics_exporter/test/plugin.js @@ -1,8 +1,9 @@ -const assert = require('assert').strict; +"use strict"; +const assert = require("assert").strict; -const mock = require('../../../test/mock'); -const instance = require('../instance'); -const info = require('../info'); +const mock = require("../../../test/mock"); +const instance = require("../instance"); +const info = require("../info"); describe("statistics_exporter plugin", function() { @@ -16,42 +17,42 @@ describe("statistics_exporter plugin", function() { describe(".onMetrics()", function() { it("should record statistics", async function() { instancePlugin.instance.server.rconCommandResults.set( - '/sc statistics_exporter.export()', + "/sc statistics_exporter.export()", JSON.stringify({ game_tick: 100, player_count: 3, force_flow_statistics: { player: { item_production_statistics: { - input: { "inserter": 10, }, - output: { "iron-plate": 24, } + input: { "inserter": 10 }, + output: { "iron-plate": 24 }, }, - } + }, }, game_flow_statistics: { pollution_statistics: { input: { "boiler": 2000 }, output: { "tree-proxy": 560 }, - } + }, }, }) ); await instancePlugin.onMetrics(); - assert.equal(instance._instancePlayerCount.labels('7357').get(), 3); - assert.equal(instance._instanceGameTicksTotal.labels('7357').get(), 100); + assert.equal(instance._instancePlayerCount.labels("7357").get(), 3); + assert.equal(instance._instanceGameTicksTotal.labels("7357").get(), 100); assert.equal(instance._instanceForceFlowStatistics.labels( - '7357', 'player', 'item_production_statistics', 'input', 'inserter').get(), + "7357", "player", "item_production_statistics", "input", "inserter").get(), 10); assert.equal(instance._instanceGameFlowStatistics.labels( - '7357', 'pollution_statistics', 'input', 'boiler').get(), + "7357", "pollution_statistics", "input", "boiler").get(), 2000); }); it("should pass on JSON parse errors", async function() { - let string = 'An error occured\n'; + let string = "An error occured\n"; instancePlugin.instance.server.rconCommandResults.set( - '/sc statistics_exporter.export()', string + "/sc statistics_exporter.export()", string ); let errorMessage; diff --git a/plugins/subspace_storage/dole.js b/plugins/subspace_storage/dole.js index f7ed8dc2..fe06bb3a 100644 --- a/plugins/subspace_storage/dole.js +++ b/plugins/subspace_storage/dole.js @@ -1,4 +1,5 @@ -const doleNN = require("./dole_nn_base") +"use strict"; +const doleNN = require("./dole_nn_base"); const prometheus = require("lib/prometheus"); @@ -15,31 +16,32 @@ const prometheusDoleFactorGauge = new prometheus.Gauge( ); class neuralDole { - - getRequestStats(itemname,samples) - { - var sum=0; - samples=Math.min(samples,(this.stats[itemname] || []).length-1); - if (samples<1) return 0.1; - for (var i = 1; i <= samples; i++) { - sum+=this.stats[itemname][i].req; - } - if (sum==0) return 0.1; - return sum/samples; - } - - constructor({ - items - }){ - // Set some storage variables for the dole divider - this.items = items - this.itemsLastTick = new Map(items._items) - this.dole = {} - this.carry = {} - this.lastRequest = {} - this.stats=[] - this.debt= {} + + getRequestStats(itemname,samples) + { + var sum=0; + samples=Math.min(samples,(this.stats[itemname] || []).length-1); + if (samples<1) return 0.1; + for (var i = 1; i <= samples; i++) { + sum+=this.stats[itemname][i].req; + } + if (sum==0) return 0.1; + return sum/samples; } + + constructor({ + items, + }){ + // Set some storage variables for the dole divider + this.items = items; + this.itemsLastTick = new Map(items._items); + this.dole = {}; + this.carry = {}; + this.lastRequest = {}; + this.stats=[]; + this.debt= {}; + } + doMagic() { for (let [name, count] of this.items._items) { let magicData = doleNN.Tick( @@ -47,84 +49,85 @@ class neuralDole { this.dole[name], this.itemsLastTick.get(name), this.getRequestStats(name,5) - ) - this.stats[name]=this.stats[name] || [] - this.stats[name].unshift({req:0,given:0});//stats[name][0] is the one we currently collect + ); + this.stats[name]=this.stats[name] || []; + this.stats[name].unshift({ req: 0, given: 0 });//stats[name][0] is the one we currently collect if (this.stats[name].length>10) this.stats[name].pop();//remove if too many samples in stats - this.dole[name] = magicData[0] + this.dole[name] = magicData[0]; // DONE handle magicData[1] for graphing for our users prometheusNNDoleGauge.labels(name).set(magicData[1] || 0); } - this.itemsLastTick = new Map(this.items._items) - } - divider(object) { - let magicData = doleNN.Dose( - object.count, // numReq + this.itemsLastTick = new Map(this.items._items); + } + + divider(object) { + let magicData = doleNN.Dose( + object.count, // numReq this.items.getItemCount(object.name), this.itemsLastTick.get(object.name) || 0, - this.dole[object.name], - this.carry[object.name+" "+object.instanceID] || 0, - this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] || 0, - this.getRequestStats(object.name,5), + this.dole[object.name], + this.carry[object.name+" "+object.instanceID] || 0, + this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] || 0, + this.getRequestStats(object.name,5), this.debt[object.name+" "+object.instanceID] || 0 - ) - if ((this.stats[object.name] || []).length>0) - { - this.stats[object.name][0].req+=Number(object.count); - this.stats[object.name][0].given+=Number(magicData[0]); - } - this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] = object.count - //0.Number of items to give in that dose - //1.New dole for item X - //2.New carry for item X slave Y - this.dole[object.name] = magicData[1] - this.carry[object.name+" "+object.instanceID] = magicData[2] - this.debt[object.name+" "+object.instanceID] = magicData[3] + ); + if ((this.stats[object.name] || []).length>0) + { + this.stats[object.name][0].req+=Number(object.count); + this.stats[object.name][0].given+=Number(magicData[0]); + } + this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] = object.count; + //0.Number of items to give in that dose + //1.New dole for item X + //2.New carry for item X slave Y + this.dole[object.name] = magicData[1]; + this.carry[object.name+" "+object.instanceID] = magicData[2]; + this.debt[object.name+" "+object.instanceID] = magicData[3]; // Remove item from DB and send it this.items.removeItem(object.name, magicData[0]); return magicData[0]; - } + } } // If the server regularly can't fulfill requests, this number grows until it can. Then it slowly shrinks back down. let _doleDivisionFactor = {}; function doleDivider({ - object, + object, items, logItemTransfers, }){ let itemCount = items.getItemCount(object.name); - const doleDivisionRetardation = 10; //lower rates will equal more dramatic swings - const maxDoleDivision = 250; //a higher cap will divide the store more ways, but will take longer to recover as supplies increase - - const originalCount = Number(object.count) || 0; - object.count /= ((_doleDivisionFactor[object.name]||0)+doleDivisionRetardation)/doleDivisionRetardation; - object.count = Math.round(object.count); + const doleDivisionRetardation = 10; //lower rates will equal more dramatic swings + const maxDoleDivision = 250; //a higher cap will divide the store more ways, but will take longer to recover as supplies increase + + const originalCount = Number(object.count) || 0; + object.count /= ((_doleDivisionFactor[object.name]||0)+doleDivisionRetardation)/doleDivisionRetardation; + object.count = Math.round(object.count); if (logItemTransfers) { - console.info(`Serving ${object.count}/${originalCount} ${object.name} from ${itemCount} ${object.name} with dole division factor ${(_doleDivisionFactor[object.name]||0)} (real=${((_doleDivisionFactor[object.name]||0)+doleDivisionRetardation)/doleDivisionRetardation}), item is ${itemCount > object.count?'stocked':'short'}.`); + console.info(`Serving ${object.count}/${originalCount} ${object.name} from ${itemCount} ${object.name} with dole division factor ${(_doleDivisionFactor[object.name]||0)} (real=${((_doleDivisionFactor[object.name]||0)+doleDivisionRetardation)/doleDivisionRetardation}), item is ${itemCount > object.count?"stocked":"short"}.`); } // Update existing items if item name already exists if (itemCount > object.count) { - //If successful, increase dole - _doleDivisionFactor[object.name] = Math.max((_doleDivisionFactor[object.name]||0)||1, 1) - 1; - items.removeItem(object.name, object.count) + //If successful, increase dole + _doleDivisionFactor[object.name] = Math.max((_doleDivisionFactor[object.name]||0)||1, 1) - 1; + items.removeItem(object.name, object.count); - prometheusDoleFactorGauge.labels(object.name).set(_doleDivisionFactor[object.name] || 0); - return object.count; - } else { - // if we didn't have enough, attempt giving out a smaller amount next time - _doleDivisionFactor[object.name] = Math.min(maxDoleDivision, Math.max((_doleDivisionFactor[object.name]||0)||1, 1) * 2); - prometheusDoleFactorGauge.labels(object.name).set(_doleDivisionFactor[object.name] || 0); - return 0; - //console.log('failure out of ' + object.name + " | " + object.count + " from " + object.instanceID + " ("+object.instanceName+")"); - } + prometheusDoleFactorGauge.labels(object.name).set(_doleDivisionFactor[object.name] || 0); + return object.count; + } else { + // if we didn't have enough, attempt giving out a smaller amount next time + _doleDivisionFactor[object.name] = Math.min(maxDoleDivision, Math.max((_doleDivisionFactor[object.name]||0)||1, 1) * 2); + prometheusDoleFactorGauge.labels(object.name).set(_doleDivisionFactor[object.name] || 0); + return 0; + //console.log('failure out of ' + object.name + " | " + object.count + " from " + object.instanceID + " ("+object.instanceName+")"); + } } module.exports = { - doleDivider, - neuralDole -} \ No newline at end of file + doleDivider, + neuralDole, +}; diff --git a/plugins/subspace_storage/dole_nn_base.js b/plugins/subspace_storage/dole_nn_base.js index ba00188c..dc978ebc 100644 --- a/plugins/subspace_storage/dole_nn_base.js +++ b/plugins/subspace_storage/dole_nn_base.js @@ -1,5 +1,6 @@ //Sigmoids... +"use strict"; function sigmoid0(x) { return x/(1+Math.abs(x)); @@ -7,17 +8,17 @@ function sigmoid0(x) function sigmoid1(x) { if (x>=0.0) { - return 1.0 + return 1.0; } else { - return 0.0 + return 0.0; } } function sigmoid2(x) { if (x>=0.0){ - return x + return x; } else { - return 0.0 + return 0.0; } } function sigmoid3(x) @@ -46,7 +47,7 @@ function Dose(numreq,instore,store_last_tick,dole,carry,prev_req,numreq_total_ad instore=Number(instore); var instore_adj=instore; if (instore_adj==0) instore_adj=0.1; - + var b=Math.trunc(prev_req/instore_adj*numreq); var inp1=[numreq,instore_adj,store_last_tick/numreq,dole,carry];//0..4 var inp2=[prev_req/numreq,numreq/numreq_total_adj,store_last_tick/numreq_total_adj,prev_req/instore_adj,b,numreq_total_adj,store_last_tick,debt];//5..12 @@ -54,30 +55,30 @@ function Dose(numreq,instore,store_last_tick,dole,carry,prev_req,numreq_total_ad var internal=new Array(20).fill(0); var outputs=[0,0,0,0]; //NN: Please don't touch :), code is generated - -outputs[0]=inputs[7]*0.8058582544; -outputs[0]=sigmoid2(outputs[0]); -outputs[1]=sigmoid0(outputs[1]); -outputs[2]=sigmoid0(outputs[2]); -outputs[3]=inputs[6]*0.2294657379; -outputs[3]=sigmoid0(outputs[3]); + + outputs[0]=inputs[7]*0.8058582544; + outputs[0]=sigmoid2(outputs[0]); + outputs[1]=sigmoid0(outputs[1]); + outputs[2]=sigmoid0(outputs[2]); + outputs[3]=inputs[6]*0.2294657379; + outputs[3]=sigmoid0(outputs[3]); debt=debt || 0; if (outputs[0]>0.99) outputs[0]=1.0; - + var res1=Number(outputs[0])*numreq+outputs[3]+debt; var res_capped=Math.round(res1); - + if (res_capped>numreq) res_capped=numreq; if (res_capped>instore) res_capped=instore; if (res_capped<0) res_capped=0; - + res1=res1 || 0; res_capped=res_capped || 0;//Safeguard against wrong values debt=res1-res_capped; debt_cap=Math.round(numreq*1.5)+5; if (debt>debt_cap) debt=debt_cap; - + carry=sigmoid0(outputs[2]*carry+outputs[0]) || 0; return [res_capped,dole+outputs[1],carry,debt]; } @@ -101,19 +102,19 @@ function Tick(instore,dole,store_last_tick,numreq_total_adj) var internal=new Array(10).fill(0); var outputs=[0,0]; //NN: Please don't touch :), code is generated - -outputs[0]=inputs[3]*0.9364774823; -outputs[0]=outputs[0]*1.0*0.4590721726; -outputs[0]=sigmoid2(outputs[0]); -outputs[1]=outputs[0]*1.828422785; -outputs[1]=sigmoid3(outputs[1]); + + outputs[0]=inputs[3]*0.9364774823; + outputs[0]=outputs[0]*0.4590721726; + outputs[0]=sigmoid2(outputs[0]); + outputs[1]=outputs[0]*1.828422785; + outputs[1]=sigmoid3(outputs[1]); avg=outputs[1]; if (numreq_total_adj<0.11) avg=1;//When no item's are requested, result is 0.1 ; In that case there is no deficit, so return 1 = 100% demand fulfilled if (avg>1) avg=1; if (avg<0) avg=0; - + return [sigmoid0(outputs[0]),avg]; } -module.exports = {Dose,Tick} \ No newline at end of file +module.exports = { Dose,Tick }; diff --git a/plugins/subspace_storage/info.js b/plugins/subspace_storage/info.js index dbbfc683..028162e8 100644 --- a/plugins/subspace_storage/info.js +++ b/plugins/subspace_storage/info.js @@ -1,3 +1,4 @@ +"use strict"; const link = require("lib/link"); const config = require("lib/config"); @@ -50,7 +51,7 @@ const items = { { type: "integer" }, ], }, -} +}; module.exports = { name: "subspace_storage", @@ -103,4 +104,4 @@ module.exports = { }, }), }, -} +}; diff --git a/plugins/subspace_storage/instance.js b/plugins/subspace_storage/instance.js index 7be30d1b..53ca3571 100644 --- a/plugins/subspace_storage/instance.js +++ b/plugins/subspace_storage/instance.js @@ -1,11 +1,12 @@ +"use strict"; const fs = require("fs-extra"); const plugin = require("lib/plugin"); const luaTools = require("lib/luaTools"); function unexpectedError(err) { - console.log("Unexpected error in subspace_storage") - console.log("------------------------------------") + console.log("Unexpected error in subspace_storage"); + console.log("------------------------------------"); console.log(err); } @@ -44,7 +45,7 @@ class InstancePlugin extends plugin.BaseInstancePlugin { }); if (this.instance.config.get("subspace_storage.log_item_transfers")) { - console.log("Exported the following to master:") + console.log("Exported the following to master:"); console.log(items); } } @@ -62,7 +63,7 @@ class InstancePlugin extends plugin.BaseInstancePlugin { } if (this.instance.config.get("subspace_storage.log_item_transfers")) { - console.log("Imported following from master:") + console.log("Imported following from master:"); console.log(response.items); } diff --git a/plugins/subspace_storage/master.js b/plugins/subspace_storage/master.js index b688fb14..9a6bdab5 100644 --- a/plugins/subspace_storage/master.js +++ b/plugins/subspace_storage/master.js @@ -47,8 +47,8 @@ class MasterPlugin extends plugin.BaseMasterPlugin { this.itemsLastUpdate = new Map(this.items._items.entries()); this.autosaveId = setInterval(() => { saveDatabase(this.master.config, this.items).catch(err => { - console.log("Unexpected error autosaving items in subspace_storage") - console.log("-----------------------------------------------------") + console.log("Unexpected error autosaving items in subspace_storage"); + console.log("-----------------------------------------------------"); console.log(err); }); }, this.master.config.get("subspace_storage.autosave_interval") * 1000); @@ -56,7 +56,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin { this.neuralDole = new dole.neuralDole({ items: this.items }); this.doleMagicId = setInterval(() => { if (this.master.config.get("subspace_storage.division_method") === "neural_dole") { - this.neuralDole.doMagic() + this.neuralDole.doMagic(); } }, 1000); @@ -97,7 +97,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin { this.updateStorage(); if (this.master.config.get("subspace_storage.log_item_transfers")) { - console.log(`Imported the following from ${message.data.instance_id}:`) + console.log(`Imported the following from ${message.data.instance_id}:`); console.log(message.data.items); } } @@ -136,7 +136,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin { } else if (method === "dole") { for (let item of message.data.items) { let count = dole.doleDivider({ - object: { name: item[0], count: item[1], instanceId, instanceName}, + object: { name: item[0], count: item[1], instanceId, instanceName }, items: this.items, logItemTransfers: this.master.config.get("subspace_storage.log_item_transfers"), }); @@ -166,7 +166,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin { return { items: itemsRemoved, - } + }; } onMetrics() { diff --git a/plugins/subspace_storage/routes.js b/plugins/subspace_storage/routes.js index 301cc371..cffa5c54 100644 --- a/plugins/subspace_storage/routes.js +++ b/plugins/subspace_storage/routes.js @@ -1,3 +1,4 @@ +"use strict"; const express = require("express"); const path = require("path"); @@ -53,4 +54,4 @@ function addApiRoutes(app, items, endpointHitCounter) { module.exports = { addWebRoutes, addApiRoutes, -} +}; diff --git a/plugins/subspace_storage/static/storage.js b/plugins/subspace_storage/static/storage.js index 8948ae9c..a2f9157d 100644 --- a/plugins/subspace_storage/static/storage.js +++ b/plugins/subspace_storage/static/storage.js @@ -1,35 +1,36 @@ // function to draw data we recieve from ajax requests +"use strict"; _lastData = []; function drawcontents(data) { data = data || _lastData; //Cache data so we can drawcontents without waiting for the server, for the search box. _lastData = data; - // allow searching for multiple criteria separated by space - var searchArgs = document.querySelector("#search").value; - searchArgs = searchArgs.trim(); - searchArgs = '(' + searchArgs.replace(/ +/g,")|(") + ')'; - const search = new RegExp(searchArgs, 'i'); - data = data.filter(function(item) { + // allow searching for multiple criteria separated by space + var searchArgs = document.querySelector("#search").value; + searchArgs = searchArgs.trim(); + searchArgs = "(" + searchArgs.replace(/ +/g,")|(") + ")"; + const search = new RegExp(searchArgs, "i"); + data = data.filter(function(item) { return search.test(item.name); - }) - + }); + sortByKey(data, "count"); - + const table = document.querySelector("#contents tbody"); //tables have tbody inserted automatically const rows = table.children; - + //update existing rows or create new ones data.forEach(function(item, i) { // format count to have .s in it for large number readability (1,000,000) item.count = item.count.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); - + let row = rows[i]; if(!row) { - row = document.createElement('tr'); + row = document.createElement("tr"); row.innerHTML = "
"; table.appendChild(row); } - + const itemIcon = row.querySelector("div"); const iconClass = getItemIconClass(item.name); if (itemIcon.getAttribute("class") !== iconClass) { @@ -51,17 +52,17 @@ function drawcontents(data) { } } - const name = row.querySelector('.name'); + const name = row.querySelector(".name"); if(name.textContent !== localeName) { name.textContent = localeName; } - - const count = row.querySelector('.count'); - if(count.textContent !== ''+item.count) { + + const count = row.querySelector(".count"); + if(count.textContent !== String(item.count)) { count.textContent = item.count; } }); - + //remove excess rows, for example, after filtering while (data.length < rows.length) { table.removeChild(rows[data.length]); @@ -76,7 +77,7 @@ function updateInventory() { let data = JSON.parse(xmlhttp.responseText); drawcontents(data); } - } + }; xmlhttp.open("GET", `${root}api/inventory`, true); xmlhttp.send(); } @@ -88,11 +89,11 @@ if(JSON.parse(localStorage.settings)["Periodically update storage screen"]) { // function to sort arrays of objects after a keys value function sortByKey(array, key) { - array.sort(function(a, b) { - return b[key] - a[key]; - }); + array.sort(function(a, b) { + return b[key] - a[key]; + }); } -document.querySelector("#search").addEventListener('input', function() { +document.querySelector("#search").addEventListener("input", function() { drawcontents(); }); diff --git a/plugins/subspace_storage/test/missing.js b/plugins/subspace_storage/test/missing.js index 7eadd460..0389e0e5 100644 --- a/plugins/subspace_storage/test/missing.js +++ b/plugins/subspace_storage/test/missing.js @@ -1,7 +1,8 @@ // These are libraries that does not have any units test, they are // required here in order to track coverage for them. -require('../instance'); -require('../master'); -require('../dole'); -require('../dole_nn_base'); +"use strict"; +require("../instance"); +require("../master"); +require("../dole"); +require("../dole_nn_base"); diff --git a/plugins/subspace_storage/test/routes.js b/plugins/subspace_storage/test/routes.js index 3a860681..cd6c9366 100644 --- a/plugins/subspace_storage/test/routes.js +++ b/plugins/subspace_storage/test/routes.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const routes = require("../routes"); @@ -18,7 +19,7 @@ describe("subspace_storage endpoints", function() { inventory.forEach(itemStack => { if(itemStack.name == "steel-plate" && itemStack.count >= 20) contains20SteelPlate = true; }); - assert(contains20SteelPlate, "Please ensure there are at least 20 steel plate in the inventory") + assert(contains20SteelPlate, "Please ensure there are at least 20 steel plate in the inventory"); }); }); }); diff --git a/routes.js b/routes.js index efd330b0..1b3c7c92 100644 --- a/routes.js +++ b/routes.js @@ -1,3 +1,4 @@ +"use strict"; module.exports = function(app) { app.get("/", function(req, res) { res.render("index"); @@ -11,4 +12,4 @@ module.exports = function(app) { app.get("/nodeDetails", function(req, res) { res.render("nodeDetails"); }); -}; \ No newline at end of file +}; diff --git a/slave.js b/slave.js index 8e949460..41466a2f 100644 --- a/slave.js +++ b/slave.js @@ -10,8 +10,9 @@ * @example * node slave run */ -const fs = require('fs-extra'); -const path = require('path'); +"use strict"; +const fs = require("fs-extra"); +const path = require("path"); const yargs = require("yargs"); const events = require("events"); const pidusage = require("pidusage"); @@ -22,14 +23,13 @@ const version = require("./package").version; // internal libraries const fileOps = require("lib/fileOps"); -const hashFile = require('lib/hash').hashFile; const factorio = require("lib/factorio"); const link = require("lib/link"); const plugin = require("lib/plugin"); const errors = require("lib/errors"); -const prometheus = require('lib/prometheus'); -const luaTools = require('lib/luaTools'); -const config = require('lib/config'); +const prometheus = require("lib/prometheus"); +const luaTools = require("lib/luaTools"); +const config = require("lib/config"); const instanceRconCommandsCounter = new prometheus.Counter( @@ -69,7 +69,7 @@ function applyAsConfig(name) { } catch (err) { console.error(`Error applying server setting ${name}`, err); } - } + }; } const serverSettingsActions = { @@ -87,7 +87,7 @@ const serverSettingsActions = { "game_password": applyAsConfig("password"), "require_user_verification": applyAsConfig("require-user-verification"), "tags": applyAsConfig("tags"), - "visibility": async function action(server, value) { + "visibility": async (server, value) => { for (let scope of ["lan", "public", "steam"]) { try { let enabled = Boolean(value[scope]); @@ -104,7 +104,7 @@ const serverSettingsActions = { */ class Instance extends link.Link{ constructor(slave, connector, dir, factorioDir, instanceConfig) { - super('instance', 'slave', connector); + super("instance", "slave", connector); link.attachAllMessages(this); this._slave = slave; this._dir = dir; @@ -125,9 +125,9 @@ class Instance extends link.Link{ let serverOptions = { version: this.config.get("factorio.version"), - gamePort: this.config.get('factorio.game_port'), - rconPort: this.config.get('factorio.rcon_port'), - rconPassword: this.config.get('factorio.rcon_password'), + gamePort: this.config.get("factorio.game_port"), + rconPort: this.config.get("factorio.rcon_port"), + rconPassword: this.config.get("factorio.rcon_password"), }; this._running = false; @@ -139,10 +139,10 @@ class Instance extends link.Link{ this.server.sendRcon = (...args) => { instanceRconCommandsCounter.labels(String(this.config.get("instance.id"))).inc(); return originalSendRcon.call(this.server, ...args); - } + }; - this.server.on('output', (output) => { - link.messages.instanceOutput.send(this, { instance_id: this.config.get("instance.id"), output }) + this.server.on("output", (output) => { + link.messages.instanceOutput.send(this, { instance_id: this.config.get("instance.id"), output }); plugin.invokeHook(this.plugins, "onOutput", output); }); @@ -181,7 +181,7 @@ class Instance extends link.Link{ for (let collector of prometheus.defaultRegistry.collectors) { if ( collector instanceof prometheus.ValueCollector - && collector.metric.labels.includes('instance_id') + && collector.metric.labels.includes("instance_id") ) { collector.removeAll({ instance_id: String(this.config.get("instance.id")) }); } @@ -225,7 +225,7 @@ class Instance extends link.Link{ } } - let plugins = {} + let plugins = {}; for (let [name, plugin] of this.plugins) { plugins[name] = plugin.info.version; } @@ -239,7 +239,8 @@ class Instance extends link.Link{ * entries from the given settings object. * * @param {Object} overrides - Server settings to override. - * @returns server example settings with the given settings applied over it. + * @returns {Object} + * server example settings with the given settings applied over it. */ async resolveServerSettings(overrides) { let serverSettings = await this.server.exampleSettings(); @@ -275,13 +276,11 @@ class Instance extends link.Link{ * Creates the neccessary files for starting up a new instance into the * provided instance directory. * - * @param {Number} id - - * ID of the new instance. Must be unique to the cluster. * @param {String} instanceDir - * Directory to create the new instance into. * @param {String} factorioDir - Path to factorio installation. */ - static async create(instanceConfig, instanceDir, factorioDir) { + static async create(instanceDir, factorioDir) { console.log(`Clusterio | Creating ${instanceDir}`); await fs.ensureDir(instanceDir); await fs.ensureDir(path.join(instanceDir, "script-output")); @@ -320,13 +319,13 @@ class Instance extends link.Link{ // Use latest save if no save was specified if (saveName === null) { saveName = await fileOps.getNewestFile( - this.path("saves"), (name) => !name.endsWith('.tmp.zip') + this.path("saves"), (name) => !name.endsWith(".tmp.zip") ); } // Create save if no save was found. if (saveName === null) { - console.log("Clusterio | Creating new save") + console.log("Clusterio | Creating new save"); await this.server.create("world.zip"); saveName = "world.zip"; } @@ -337,12 +336,12 @@ class Instance extends link.Link{ // Find plugin modules to patch in let modules = new Map(); for (let [pluginName, plugin] of this.plugins) { - let modulePath = path.join('plugins', pluginName, 'module'); + let modulePath = path.join("plugins", pluginName, "module"); if (!await fs.pathExists(modulePath)) { continue; } - let moduleJsonPath = path.join(modulePath, 'module.json'); + let moduleJsonPath = path.join(modulePath, "module.json"); if (!await fs.pathExists(moduleJsonPath)) { throw new Error(`Module for plugin ${pluginName} is missing module.json`); } @@ -352,25 +351,26 @@ class Instance extends link.Link{ throw new Error(`Expected name of module for plugin ${pluginName} to match the plugin name`); } - module = Object.assign({ + module = { version: plugin.info.version, - dependencies: { 'clusterio': '*' }, + dependencies: { "clusterio": "*" }, path: modulePath, load: [], require: [], - }, module); + ...module, + }; modules.set(module.name, module); } // Find stand alone modules to load // XXX for now it's assumed all available modules should be loaded. - for (let entry of await fs.readdir('modules', { withFileTypes: true })) { + for (let entry of await fs.readdir("modules", { withFileTypes: true })) { if (entry.isDirectory()) { if (modules.has(entry.name)) { throw new Error(`Module with name ${entry.name} already exists in a plugin`); } - let moduleJsonPath = path.join('modules', entry.name, 'module.json'); + let moduleJsonPath = path.join("modules", entry.name, "module.json"); if (!await fs.pathExists(moduleJsonPath)) { throw new Error(`Module ${entry.name} is missing module.json`); } @@ -380,12 +380,13 @@ class Instance extends link.Link{ throw new Error(`Expected name of module ${entry.name} to match the directory name`); } - module = Object.assign({ - path: path.join('modules', entry.name), - dependencies: { 'clusterio': '*' }, + module = { + path: path.join("modules", entry.name), + dependencies: { "clusterio": "*" }, load: [], require: [], - }, module); + ...module, + }; modules.set(module.name, module); } } @@ -402,13 +403,13 @@ class Instance extends link.Link{ * @param {String} saveName - Name of save game to load. */ async start(saveName) { - this.server.on('rcon-ready', () => { + this.server.on("rcon-ready", () => { console.log("Clusterio | RCON connection established"); }); this.server.on("exit", () => this.notifyExit()); await this.server.start(saveName); - await this.server.disableAchievements() + await this.server.disableAchievements(); await this.updateInstanceData(); await plugin.invokeHook(this.plugins, "onStart"); @@ -459,12 +460,12 @@ class Instance extends link.Link{ } async getMetricsRequestHandler() { - let results = [] + let results = []; if (this._running) { let pluginResults = await plugin.invokeHook(this.plugins, "onMetrics"); for (let metricIterator of pluginResults) { for await (let metric of metricIterator) { - results.push(prometheus.serializeResult(metric)) + results.push(prometheus.serializeResult(metric)); } } } @@ -482,7 +483,7 @@ class Instance extends link.Link{ async startInstanceRequestHandler(message) { let saveName = message.data.save; try { - saveName = await this.prepare(saveName) + saveName = await this.prepare(saveName); } catch (err) { this.notifyExit(); throw err; @@ -536,7 +537,7 @@ class Instance extends link.Link{ }, }); if (response.statusCode !== 200) { - throw Error(`Upload failed: ${response.statusCode} ${response.statusMessage}: ${response.body}`) + throw Error(`Upload failed: ${response.statusCode} ${response.statusMessage}: ${response.body}`); } } finally { @@ -569,6 +570,8 @@ class Instance extends link.Link{ * the directory of the instance. For example instance.path("mods") * returns a path to the mods directory of the instance. If no parts are * given it returns a path to the directory of the instance. + * + * @returns {string} path in instance directory. */ path(...parts) { return path.join(this._dir, ...parts); @@ -585,7 +588,7 @@ class Instance extends link.Link{ * @param {Map} instanceInfos - * mapping between instance id and information about this instance. * @param {string} instancesDir - Directory containing instances - * @param logger - console like logging interface. + * @param {Object} logger - console like logging interface. */ async function discoverInstances(instanceInfos, instancesDir, logger) { for (let entry of await fs.readdir(instancesDir, { withFileTypes: true })) { @@ -622,7 +625,7 @@ async function discoverInstances(instanceInfos, instancesDir, logger) { class InstanceConnection extends link.Link { constructor(connector, slave, instanceId) { - super('slave', 'instance', connector); + super("slave", "instance", connector); this.slave = slave; this.instanceId = instanceId; this.plugins = new Map(); @@ -717,7 +720,7 @@ class Slave extends link.Link { // I don't like God classes, but the alternative of putting all this state // into global variables is not much better. constructor(connector, slaveConfig, pluginInfos) { - super('slave', 'master', connector); + super("slave", "master", connector); link.attachAllMessages(this); this.pluginInfos = pluginInfos; @@ -750,9 +753,9 @@ class Slave extends link.Link { setBlocking(true); console.error("ERROR: Unexpected error during shutdown"); console.error(err); - process.exit(1) + process.exit(1); }); - });; + }); }); this.connector.on("close", () => { @@ -769,7 +772,7 @@ class Slave extends link.Link { setBlocking(true); console.error("ERROR: Unexpected error during shutdown"); console.error(err); - process.exit(1) + process.exit(1); }); }); @@ -779,7 +782,7 @@ class Slave extends link.Link { setBlocking(true); console.error("ERROR: Unexpected error during shutdown"); console.error(err); - process.exit(1) + process.exit(1); }); } }); @@ -795,7 +798,7 @@ class Slave extends link.Link { async _findNewInstanceDir(name) { try { - checkFilename(name) + checkFilename(name); } catch (err) { throw new Error(`Instance name ${err.message}`); } @@ -803,7 +806,7 @@ class Slave extends link.Link { // For now add dashes until an unused directory name is found let dir = path.join(this.config.get("slave.instances_directory"), name); while (await fs.pathExists(dir)) { - dir += '-'; + dir += "-"; } return dir; @@ -821,7 +824,7 @@ class Slave extends link.Link { } if (request.plugin && !instanceConnection.plugins.has(request.plugin)) { - throw new errors.RequestError(`Instance ID ${instanceId} does not have ${request.plugin} plugin loaded`) + throw new errors.RequestError(`Instance ID ${instanceId} does not have ${request.plugin} plugin loaded`); } return await request.send(instanceConnection, message.data); @@ -861,7 +864,7 @@ class Slave extends link.Link { // XXX: race condition on multiple simultanious calls let instanceDir = await this._findNewInstanceDir(instanceConfig.get("instance.name")); - await Instance.create(instanceConfig, instanceDir, this.config.get("slave.factorio_directory")); + await Instance.create(instanceDir, this.config.get("slave.factorio_directory")); instanceInfo = { path: instanceDir, config: instanceConfig, @@ -872,10 +875,10 @@ class Slave extends link.Link { // save a copy of the instance config - let warnedOutput = Object.assign( - { _warning: "Changes to this file will be overwritten by the master server's copy." }, - instanceInfo.config.serialize() - ); + let warnedOutput = { + _warning: "Changes to this file will be overwritten by the master server's copy.", + ...instanceInfo.config.serialize(), + }; await fs.outputFile( path.join(instanceInfo.path, "instance.json"), JSON.stringify(warnedOutput, null, 4) @@ -884,6 +887,9 @@ class Slave extends link.Link { /** * Initialize and connect an unloaded instance + * + * @param {number} instanceId - ID of instance to initialize. + * @returns {module:slave~InstanceConnection} connection to instance. */ async _connectInstance(instanceId) { let instanceInfo = this.instanceInfos.get(instanceId); @@ -919,10 +925,10 @@ class Slave extends link.Link { } for await (let result of prometheus.defaultRegistry.collect()) { - if (result.metric.name.startsWith('process_')) { + if (result.metric.name.startsWith("process_")) { results.push(prometheus.serializeResult(result, { - addLabels: { 'slave_id': String(this.config.get("slave.id")) }, - metricName: result.metric.name.replace('process_', 'clusterio_slave_'), + addLabels: { "slave_id": String(this.config.get("slave.id")) }, + metricName: result.metric.name.replace("process_", "clusterio_slave_"), })); } else { @@ -1034,10 +1040,10 @@ function checkFilename(name) { const oneToNine = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const badNames = [ // Relative path components - '.', '..', + ".", "..", // Reserved filenames in Windows - 'CON', 'PRN', 'AUX', 'NUL', + "CON", "PRN", "AUX", "NUL", ...oneToNine.map(n => `COM${n}`), ...oneToNine.map(n => `LPT${n}`), ]; @@ -1099,7 +1105,7 @@ async function symlinkMods(instance, sharedMods, logger) { let instanceModsEntries = new Set(await fs.readdir(instance.path("mods"))); for (let entry of await fs.readdir(sharedMods, { withFileTypes: true })) { if (entry.isFile()) { - if (['.zip', '.dat'].includes(path.extname(entry.name))) { + if ([".zip", ".dat"].includes(path.extname(entry.name))) { if (!instanceModsEntries.has(entry.name)) { logger.log(`linking ${entry.name} from ${sharedMods}`); let target = path.join(sharedMods, entry.name); @@ -1130,17 +1136,17 @@ async function symlinkMods(instance, sharedMods, logger) { async function startSlave() { // add better stack traces on promise rejection - process.on('unhandledRejection', r => console.log(r)); + process.on("unhandledRejection", r => console.log(r)); // argument parsing const args = yargs .scriptName("slave") .usage("$0 [options]") - .option('config', { + .option("config", { nargs: 1, describe: "slave config file to use", - default: 'config-slave.json', - type: 'string', + default: "config-slave.json", + type: "string", }) .command("config", "Manage Slave config", config.configCommand) .command("run", "Run slave") @@ -1160,7 +1166,7 @@ async function startSlave() { await slaveConfig.load(JSON.parse(await fs.readFile(args.config))); } catch (err) { - if (err.code === 'ENOENT') { + if (err.code === "ENOENT") { console.log("Config not found, initializing new config"); await slaveConfig.init(); @@ -1210,8 +1216,8 @@ async function startSlave() { let slave = new Slave(slaveConnector, slaveConfig, pluginInfos); // Handle interrupts - let secondSigint = false - process.on('SIGINT', () => { + let secondSigint = false; + process.on("SIGINT", () => { if (secondSigint) { console.log("Caught second interrupt, terminating immediately"); process.exit(1); @@ -1232,37 +1238,6 @@ async function startSlave() { }); await slaveConnector.connect(); - - /* - } else if (command == "manage"){ - await manage(config, instance); - // process.exit(0); - */ -} - -// string, function -// returns [{modName:string,hash:string}, ... ] -function hashMods(instance, callback) { - if(!callback) { - throw new Error("ERROR in function hashMods NO CALLBACK"); - } - - function hashMod(name) { - if (path.extname(name) != ".zip") { - // Can't hash unzipped mods, return null that's filtered out later - return null; - } else { - return hashFile(instance.path("mods", name)).then(hash => ( - {modName: name, hash: hash} - )); - } - } - - let promises = fs.readdirSync(instance.path("mods")).map(hashMod); - Promise.all(promises).then(hashes => { - // Remove null entries from hashMod - callback(hashes.filter(entry => entry !== null)); - }); } module.exports = { diff --git a/test/clusterctl.js b/test/clusterctl.js index 8dd1c36e..acb6c5b0 100644 --- a/test/clusterctl.js +++ b/test/clusterctl.js @@ -1,10 +1,11 @@ -const assert = require('assert').strict; -const chalk = require('chalk'); +"use strict"; +const assert = require("assert").strict; +const chalk = require("chalk"); -const errors = require('lib/errors'); -const { testLines } = require('./lib/factorio/lines'); -const clusterctl = require('../clusterctl.js'); -const mock = require('./mock'); +const errors = require("lib/errors"); +const { testLines } = require("./lib/factorio/lines"); +const clusterctl = require("../clusterctl.js"); +const mock = require("./mock"); describe("clusterctl", function() { @@ -24,13 +25,13 @@ describe("clusterctl", function() { }); let mockConnector = new mock.MockConnector(); - mockConnector.on('send', function(message) { - if (message.type === 'list_instances_request') { - this.emit('message', { - seq: 1, type: 'list_instances_response', + mockConnector.on("send", function(message) { + if (message.type === "list_instances_request") { + this.emit("message", { + seq: 1, type: "list_instances_response", data: { seq: message.seq, - list: [{ id: 57, assigned_slave: 4, name: 'Test Instance' }], + list: [{ id: 57, assigned_slave: 4, name: "Test Instance" }], }, }); } diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index 18fe1b80..73b5ee4e 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -1,8 +1,9 @@ +"use strict"; const assert = require("assert").strict; const fs = require("fs-extra"); const path = require("path"); -const validateHTML = require('html5-validator'); -const parallel = require('mocha.parallel'); +const validateHTML = require("html5-validator"); +const parallel = require("mocha.parallel"); const link = require("lib/link"); @@ -113,7 +114,7 @@ describe("Integration of Clusterio", function() { ], [ "ignore_player_limit_for_returning_players", true, - "ignore-player-limit-for-returning-players", "Ignore player limit for returning players: true." + "ignore-player-limit-for-returning-players", "Ignore player limit for returning players: true.", ], [ "max_players", 11, @@ -155,7 +156,7 @@ describe("Integration of Clusterio", function() { ], ]; - for (let [prop, value, , ] of testConfigs) { + for (let [prop, value, ,] of testConfigs) { value = `"'${JSON.stringify(value).replace(/"/g, process.platform === "win32" ? '""' : '\\"')}'"`; let args = `--instance test --field factorio.settings --prop ${prop} --value ${value}`; await exec(`node clusterctl --config ${controlConfigPath} set-instance-config-prop ${args}`); @@ -202,7 +203,7 @@ describe("Integration of Clusterio", function() { await exec(`node clusterctl --config ${controlConfigPath} create-role --name temp --description "A temp role" --permissions core.control.connect`); let result = await link.messages.listRoles.send(getControl()); let role = result.list.find(role => role.name === "temp"); - assert.deepEqual(role, { id: 5, name: "temp", description: "A temp role", permissions: ["core.control.connect"]}); + assert.deepEqual(role, { id: 5, name: "temp", description: "A temp role", permissions: ["core.control.connect"] }); }); }); @@ -211,7 +212,7 @@ describe("Integration of Clusterio", function() { await exec(`node clusterctl --config ${controlConfigPath} edit-role --role temp --name new --description "A new role" --permissions`); let result = await link.messages.listRoles.send(getControl()); let role = result.list.find(role => role.name === "new"); - assert.deepEqual(role, { id: 5, name: "new", description: "A new role", permissions: []}); + assert.deepEqual(role, { id: 5, name: "new", description: "A new role", permissions: [] }); }); }); diff --git a/test/integration/export.js b/test/integration/export.js index 4fc72d8f..6c6fc233 100644 --- a/test/integration/export.js +++ b/test/integration/export.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const path = require("path"); diff --git a/test/integration/index.js b/test/integration/index.js index e8fe86ce..c7305c0a 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -1,3 +1,4 @@ +"use strict"; const path = require("path"); const fs = require("fs-extra"); const child_process = require("child_process"); @@ -22,6 +23,7 @@ class TestControl extends link.Link { } async debugWsMessageEventHandler() { } + async instanceOutputEventHandler() { } } @@ -34,6 +36,7 @@ class TestControlConnector extends link.WebSocketClientConnector { // Mark that this test takes a lot of time, or depeneds on a test // that takes a lot of time. function slowTest(test) { + // eslint-disable-next-line no-process-env if (process.env.FAST_TEST) { test.skip(); } @@ -67,7 +70,7 @@ async function exec(...args) { } async function sendRcon(instanceId, command) { - let response = await link.messages.sendRcon.send(control, { instance_id: instanceId, command }) + let response = await link.messages.sendRcon.send(control, { instance_id: instanceId, command }); return response.result; } @@ -86,8 +89,8 @@ function spawn(name, cmd, waitFor) { resolve(process); } console.log(name, line); - }) - let stderr = new server._LineSplitter((line) => { console.log(name, line.toString("utf8")); }) + }); + let stderr = new server._LineSplitter((line) => { console.log(name, line.toString("utf8")); }); process.stdout.on("data", chunk => { stdout.data(chunk); }); process.stdout.on("close", () => { stdout.end(); }); process.stderr.on("data", chunk => { stderr.data(chunk); }); diff --git a/test/integration/link.js b/test/integration/link.js index ed3e5196..dec7133f 100644 --- a/test/integration/link.js +++ b/test/integration/link.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const events = require("events"); const jwt = require("jsonwebtoken"); diff --git a/test/integration/patch.js b/test/integration/patch.js index 1101e147..1baf6b1b 100644 --- a/test/integration/patch.js +++ b/test/integration/patch.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const fs = require("fs-extra"); const jszip = require("jszip"); @@ -9,7 +10,7 @@ const { slowTest } = require("./index"); // The server integration test is required to run before this one -require("./server") +require("./server"); describe("Integration of lib/factorio/patch", function() { describe("patch()", function() { diff --git a/test/integration/server.js b/test/integration/server.js index dad51b53..810570c1 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const events = require("events"); const fs = require("fs-extra"); @@ -26,13 +27,13 @@ describe("Integration of lib/factorio/server", function() { before(async function() { // Delete result from previous run of these tests - if (fs.existsSync(writePath)) { + if (await fs.pathExists(writePath)) { await fs.remove(writePath); } await fs.ensureDir(writePath); logFile = fs.createWriteStream(path.join(writePath, "log.txt"), "utf8"); - server.on('output', function(output) { + server.on("output", function(output) { logFile.write(JSON.stringify(output) + "\n"); }); @@ -119,7 +120,7 @@ describe("Integration of lib/factorio/server", function() { log(".sendRcon()"); let result = await server.sendRcon("/sc rcon.print('success')"); - assert.equal(result, 'success\n'); + assert.equal(result, "success\n"); }); it("throws on non-empty response when enabled", async function() { slowTest(this); @@ -149,20 +150,20 @@ describe("Integration of lib/factorio/server", function() { slowTest(this); log(".startScenario()"); - let pass = false + let pass = false; function filter(output) { if (output.message === "test_scenario init") { pass = true; } } - server.on('output', filter); + server.on("output", filter); await server.startScenario("test_scenario"); log(".stop()"); await server.stop(); - server.off('output', filter); + server.off("output", filter); assert(pass, "server did not output line from test scenario"); }); }); @@ -191,7 +192,7 @@ describe("Integration of lib/factorio/server", function() { await server.start("test.zip"); if (!server._rconReady) { - await events.once(server, 'rcon-ready'); + await events.once(server, "rcon-ready"); } server.sendRcon("/c while true do end").catch(() => {}); await new Promise((resolve) => setTimeout(resolve, 300)); @@ -210,7 +211,7 @@ describe("Integration of lib/factorio/server", function() { log(".start() for kill detection"); let startPromise = server.start("test.zip"); - server.once('output', () => server._server.kill('SIGKILL')); + server.once("output", () => server._server.kill("SIGKILL")); await assert.rejects( startPromise, diff --git a/test/lib/config/classes.js b/test/lib/config/classes.js index e094e4a2..802a8860 100644 --- a/test/lib/config/classes.js +++ b/test/lib/config/classes.js @@ -25,35 +25,35 @@ describe("lib/config/classes", function() { for (let [def, error] of [ [ { name: "a", type: "invalid", optional: true }, - "invalid is not a valid type" + "invalid is not a valid type", ], [ { name: [], type: "string", optional: true }, - "name must be a string" + "name must be a string", ], [ { name: "a", type: "string", enum: true, optional: true }, - "enum must be an array" + "enum must be an array", ], [ { name: "a", title: 2, type: "string", optional: true }, - "title must be a string" + "title must be a string", ], [ { name: "a", description: 2, type: "string", optional: true }, - "description must be a string" + "description must be a string", ], [ { name: "a", type: "string", optional: "yes" }, - "optional must be a boolean" + "optional must be a boolean", ], [ { name: "a", type: "string", initial_value: 1 }, - "initial_value must match the type or be a function" + "initial_value must match the type or be a function", ], [ { name: "a", type: "string" }, - "Non-optional field a needs an initial_value" + "Non-optional field a needs an initial_value", ], ]) { assert.throws(() => TestGroup.define(def), new Error(error)); @@ -97,13 +97,13 @@ describe("lib/config/classes", function() { enum: ["a", "b", "c"], optional: true, initial_value: "b", - } + }; TestGroup.define(field); // Generated properties field.fullName = "test_group.enum"; assert.deepEqual(TestGroup._definitions.get("enum"), field); - }) + }); }); describe("get definitions", function() { @@ -191,7 +191,7 @@ describe("lib/config/classes", function() { func: 22, bool: null, json: { valid: true }, - }}) + }}); assert.equal(testInstance.get("enum"), "c"); assert.equal(testInstance.get("test"), "blah"); @@ -218,7 +218,7 @@ describe("lib/config/classes", function() { alpha: null, beta: "decay", gamma: 99, - } + }; let testInstance = new TestGroup(); await testInstance.load(null, { name: "test_group", fields: testFields }); @@ -253,11 +253,11 @@ describe("lib/config/classes", function() { new Error("Expected fields to be an object, not undefined") ); assert.throws( - () => testInstance.update({ name: "test_group", fields: []}, false), + () => testInstance.update({ name: "test_group", fields: [] }, false), new Error("Expected fields to be an object, not array") ); assert.throws( - () => testInstance.update({ name: "test_group", fields: null}, false), + () => testInstance.update({ name: "test_group", fields: null }, false), new Error("Expected fields to be an object, not null") ); }); @@ -277,43 +277,43 @@ describe("lib/config/classes", function() { () => testInstance.set("bar", 1), new classes.InvalidField("No field named 'bar'") ); - }) + }); it("should throw if field is not in enum", function() { assert.throws( () => testInstance.set("enum", "bar"), new Error("Expected one of [a, b, c], not bar") ); - }) + }); it("should work if field is in enum", function() { - testInstance.set("enum", "c"), + testInstance.set("enum", "c"); assert.equal(testInstance.get("enum"), "c"); - }) + }); it("should throw if field is not optional", function() { assert.throws( () => testInstance.set("func", null), new Error("Field func cannot be null") ); - }) + }); it("should work if field is optional", function() { - testInstance.set("test", "spam"), + testInstance.set("test", "spam"); assert.equal(testInstance.get("test"), "spam"); - }) + }); it("should throw if field is of wrong type", function() { assert.throws( () => testInstance.set("test", 1), new Error("Expected type of test to be string, not number") ); - }) + }); it("should treat empty string as null", function() { - testInstance.set("test", ""), + testInstance.set("test", ""); assert.equal(testInstance.get("test"), null); - }) + }); it("should auto convert string to boolean if possible", function() { testInstance.set("bool", "true"); @@ -374,14 +374,14 @@ describe("lib/config/classes", function() { () => testInstance.setProp("bar", 1), new classes.InvalidField("No field named 'bar'") ); - }) + }); it("should throw if field is not an object", function() { assert.throws( () => testInstance.setProp("enum", "a"), new classes.InvalidField("Cannot set property on non-object field 'enum'") ); - }) + }); it("should work if field is an object", function() { testInstance.set("json", { prev: 32, test: false }); @@ -451,7 +451,8 @@ describe("lib/config/classes", function() { }); it("should construct a finalized class", function() { TestConfig.finalize(); - new TestConfig(); + let config = new TestConfig(); + assert(config); }); }); @@ -461,7 +462,7 @@ describe("lib/config/classes", function() { await testInstance.init(); assert.equal(testInstance.get("alpha.foo"), null); assert.deepEqual(testInstance.get("beta.bar"), {}); - }) + }); }); describe(".serialize()", function() { @@ -477,10 +478,10 @@ describe("lib/config/classes", function() { { name: "beta", fields: { bar: {} }, - } - ] + }, + ], }); - }) + }); }); describe(".load()", function() { @@ -521,8 +522,8 @@ describe("lib/config/classes", function() { { name: "beta", fields: { bar: { value: 20 }}, - } - ] + }, + ]; let testInstance = new TestConfig(); await testInstance.load({ groups: testGroups }); @@ -543,7 +544,7 @@ describe("lib/config/classes", function() { { name: "beta", fields: { bar: { value: 30 }}, - } + }, ]}, false); assert.deepEqual(testInstance.serialize(), { @@ -559,10 +560,10 @@ describe("lib/config/classes", function() { { name: "beta", fields: { bar: { value: 30 }}, - } + }, ], }); - }) + }); }); describe(".group()", function() { @@ -580,14 +581,14 @@ describe("lib/config/classes", function() { let testInstance = new TestConfig(); await testInstance.init(); - assert.throws(() => testInstance.get("invalid"), new Error("No config group named 'invalid'")) + assert.throws(() => testInstance.get("invalid"), new Error("No config group named 'invalid'")); }); it("should throw if no field is specified", async function() { let testInstance = new TestConfig(); await testInstance.init(); - assert.throws(() => testInstance.get("alpha"), new Error("No field named ''")) + assert.throws(() => testInstance.get("alpha"), new Error("No field named ''")); }); }); diff --git a/test/lib/database.js b/test/lib/database.js index f2f31fc9..2b4670bc 100644 --- a/test/lib/database.js +++ b/test/lib/database.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const fs = require("fs-extra"); const path = require("path"); @@ -12,21 +13,21 @@ describe("lib/database", function() { new Error("Expected all keys to be string but got number") ); assert.throws( - () => database.mapToObject(new Map([[Symbol(), 1]])), + () => database.mapToObject(new Map([[Symbol("test"), 1]])), new Error("Expected all keys to be string but got symbol") ); }); it("should throw convert a map to an object", function() { assert.deepEqual( - database.mapToObject(new Map([['a', 1], ['b', true]])), - {'a': 1, 'b': true} + database.mapToObject(new Map([["a", 1], ["b", true]])), + { "a": 1, "b": true } ); }); }); describe("loadJsonAsMap()", function() { - let badTypes = ['null', 'array', 'number', 'string', 'boolean']; + let badTypes = ["null", "array", "number", "string", "boolean"]; for (let type of badTypes) { it(`should reject on ${type} JSON`, async function() { @@ -47,7 +48,7 @@ describe("lib/database", function() { it("should work on object JSON", async function() { assert.deepEqual( await database.loadJsonAsMap(path.join("test", "file", "json", "load_map.json")), - new Map([['a', 1], ['b', true]]) + new Map([["a", 1], ["b", true]]) ); }); @@ -67,7 +68,7 @@ describe("lib/database", function() { await fs.unlink(testFile); } catch (err) { /* istanbul ignore if */ - if (err.code !== 'ENOENT') { + if (err.code !== "ENOENT") { throw err; } } @@ -75,18 +76,18 @@ describe("lib/database", function() { await deleteTestFile(); await database.saveMapAsJson( - testFile, new Map([['c', {}], ['d', "foo"]]) + testFile, new Map([["c", {}], ["d", "foo"]]) ); assert.equal( - await fs.readFile(testFile, {encoding: 'utf-8'}), + await fs.readFile(testFile, { encoding: "utf-8" }), '{\n "c": {},\n "d": "foo"\n}' ); }); }); describe("loadJsonArrayAsMap()", function() { - let badTypes = ['null', 'object', 'number', 'string', 'boolean']; + let badTypes = ["null", "object", "number", "string", "boolean"]; for (let type of badTypes) { it(`should reject on ${type} JSON`, async function() { @@ -121,7 +122,7 @@ describe("lib/database", function() { it("should work on array JSON", async function() { assert.deepEqual( await database.loadJsonArrayAsMap(path.join("test", "file", "json", "load_array_map.json")), - new Map([['a', {id: 'a'}], ['b', {id: 'b'}]]) + new Map([["a", { id: "a" }], ["b", { id: "b" }]]) ); }); @@ -141,7 +142,7 @@ describe("lib/database", function() { await fs.unlink(testFile); } catch (err) { /* istanbul ignore if */ - if (err.code !== 'ENOENT') { + if (err.code !== "ENOENT") { throw err; } } @@ -149,11 +150,11 @@ describe("lib/database", function() { await deleteTestFile(); await database.saveMapAsJsonArray( - testFile, new Map([['c', {id: 'c'}], ['d', {id: 'd'}]]) + testFile, new Map([["c", { id: "c" }], ["d", { id: "d" }]]) ); assert.equal( - await fs.readFile(testFile, {encoding: 'utf-8'}), + await fs.readFile(testFile, { encoding: "utf-8" }), '[\n {\n "id": "c"\n },\n {\n "id": "d"\n }\n]' ); }); @@ -167,18 +168,18 @@ describe("lib/database", function() { }); it("should restore the passed serialized database", function() { - let items = new database.ItemDatabase({'a': 1, 'b': 2}); - assert.deepEqual(items._items, new Map([['a', 1], ['b', 2]])); + let items = new database.ItemDatabase({ "a": 1, "b": 2 }); + assert.deepEqual(items._items, new Map([["a", 1], ["b", 2]])); }); it("should throw on invalid serialized database", function() { assert.throws( - () => new database.ItemDatabase({'a': NaN}), + () => new database.ItemDatabase({ "a": NaN }), new Error("count must be a number") ); assert.throws( - () => new database.ItemDatabase({'a': 'a'}), + () => new database.ItemDatabase({ "a": "a" }), new Error("count must be a number") ); }); @@ -186,32 +187,32 @@ describe("lib/database", function() { describe(".serialize()", function() { it("should return a serialized database", function() { - let items = new database.ItemDatabase({'a': 10}); - assert.deepEqual(items.serialize(), {'a': 10}); + let items = new database.ItemDatabase({ "a": 10 }); + assert.deepEqual(items.serialize(), { "a": 10 }); }); it("should remove zero count entries", function() { - let items = new database.ItemDatabase({'a': 0}); + let items = new database.ItemDatabase({ "a": 0 }); assert.deepEqual(items.serialize(), {}); }); }); describe(".size", function() { it("should give an approximate size of the database", function() { - let items = new database.ItemDatabase({'a': 10}); + let items = new database.ItemDatabase({ "a": 10 }); assert.equal(items.size, 1); }); }); describe(".getItemCount()", function() { it("should return the count of the given item", function() { - let items = new database.ItemDatabase({'a': 10}); - assert.equal(items.getItemCount('a'), 10); + let items = new database.ItemDatabase({ "a": 10 }); + assert.equal(items.getItemCount("a"), 10); }); it("should return zero if item does not exist", function() { let items = new database.ItemDatabase(); - assert.equal(items.getItemCount('b'), 0); + assert.equal(items.getItemCount("b"), 0); }); it("should throw on invalid name", function() { @@ -226,15 +227,15 @@ describe("lib/database", function() { describe(".addItem()", function() { it("should add a new item", function() { let items = new database.ItemDatabase(); - items.addItem('a', 10); - assert.deepEqual(items._items, new Map([['a', 10]])); - }) + items.addItem("a", 10); + assert.deepEqual(items._items, new Map([["a", 10]])); + }); it("should add an existing item", function() { - let items = new database.ItemDatabase({'a': 10}); - items.addItem('a', 10); - assert.deepEqual(items._items, new Map([['a', 20]])); - }) + let items = new database.ItemDatabase({ "a": 10 }); + items.addItem("a", 10); + assert.deepEqual(items._items, new Map([["a", 20]])); + }); it("should throw on invalid name", function() { let items = new database.ItemDatabase(); @@ -247,11 +248,11 @@ describe("lib/database", function() { it("should throw on invalid count", function() { let items = new database.ItemDatabase(); assert.throws( - () => items.addItem('a', NaN), + () => items.addItem("a", NaN), new Error("count must be a number") ); assert.throws( - () => items.addItem('a', "1"), + () => items.addItem("a", "1"), new Error("count must be a number") ); }); @@ -259,16 +260,16 @@ describe("lib/database", function() { describe(".removeItem()", function() { it("should remove an existing item", function() { - let items = new database.ItemDatabase({'a': 20}); - items.removeItem('a', 10); - assert.deepEqual(items._items, new Map([['a', 10]])); - }) + let items = new database.ItemDatabase({ "a": 20 }); + items.removeItem("a", 10); + assert.deepEqual(items._items, new Map([["a", 10]])); + }); it("should turn a non-existing item negative", function() { let items = new database.ItemDatabase(); - items.removeItem('a', 10); - assert.deepEqual(items._items, new Map([['a', -10]])); - }) + items.removeItem("a", 10); + assert.deepEqual(items._items, new Map([["a", -10]])); + }); it("should throw on invalid name", function() { let items = new database.ItemDatabase(); @@ -281,11 +282,11 @@ describe("lib/database", function() { it("should throw on invalid count", function() { let items = new database.ItemDatabase(); assert.throws( - () => items.removeItem('a', 'b'), + () => items.removeItem("a", "b"), new Error("count must be a number") ); assert.throws( - () => items.removeItem('a', "1"), + () => items.removeItem("a", "1"), new Error("count must be a number") ); }); diff --git a/test/lib/factorio/export.js b/test/lib/factorio/export.js index 1c3179b4..d4ce5691 100644 --- a/test/lib/factorio/export.js +++ b/test/lib/factorio/export.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const path = require("path"); @@ -17,5 +18,5 @@ describe("lib/factorio/export", function() { let locale = await factorio._exportLocale(testServer, new Map(), ["base"], "en"); assert.deepEqual(locale, new Map([["test.key-a", "1"], ["test.key-b", "2"]])); }); - });; + }); }); diff --git a/test/lib/factorio/lines.js b/test/lib/factorio/lines.js index 7d34fdd5..d5ced40e 100644 --- a/test/lib/factorio/lines.js +++ b/test/lib/factorio/lines.js @@ -1,113 +1,114 @@ // Lines for testing the factorio output +"use strict"; let testLines = new Map([ [ " 1.306 Info RemoteCommandProcessor.cpp:131: Starting RCON interface at port 4000", { - format: 'seconds', - time: '1.306', - type: 'log', - level: 'Info', + format: "seconds", + time: "1.306", + type: "log", + level: "Info", file: "RemoteCommandProcessor.cpp:131", message: "Starting RCON interface at port 4000", - } + }, ], [ " 0.622 Warning FileUtil.cpp:527: test not found; using test.zip", { - format: 'seconds', - time: '0.622', - type: 'log', - level: 'Warning', + format: "seconds", + time: "0.622", + type: "log", + level: "Warning", file: "FileUtil.cpp:527", message: "test not found; using test.zip", - } + }, ], [ " 0.554 Error Main.cpp:839: require_user_verification must be enabled for public games.", { - format: 'seconds', - time: '0.554', - type: 'log', - level: 'Error', + format: "seconds", + time: "0.554", + type: "log", + level: "Error", file: "Main.cpp:839", message: "require_user_verification must be enabled for public games.", - } + }, ], [ " 640.910 Quitting: remote-quit.", { - format: 'seconds', - time: '640.910', - type: 'generic', + format: "seconds", + time: "640.910", + type: "generic", message: "Quitting: remote-quit.", - } + }, ], [ "1641.421 Goodbye", { - format: 'seconds', - time: '1641.421', - type: 'generic', + format: "seconds", + time: "1641.421", + type: "generic", message: "Goodbye", - } + }, ], [ "2019-01-20 10:30:04 [JOIN] User joined the game", { - format: 'date', - time: '2019-01-20 10:30:04', - type: 'action', + format: "date", + time: "2019-01-20 10:30:04", + type: "action", action: "JOIN", message: "User joined the game", - } + }, ], [ "2019-01-20 10:30:07 [CHAT] User: chat message", { - format: 'date', - time: '2019-01-20 10:30:07', - type: 'action', + format: "date", + time: "2019-01-20 10:30:07", + type: "action", action: "CHAT", message: "User: chat message", - } + }, ], [ "2019-01-20 10:30:14 [COMMAND] User (command): blah", { - format: 'date', - time: '2019-01-20 10:30:14', - type: 'action', + format: "date", + time: "2019-01-20 10:30:14", + type: "action", action: "COMMAND", message: "User (command): blah", - } + }, ], [ "2019-01-20 10:30:14 Cannot execute command. Error: [string \"blah\"]:1: syntax error near ", { - format: 'date', - time: '2019-01-20 10:30:14', - type: 'generic', + format: "date", + time: "2019-01-20 10:30:14", + type: "generic", message: "Cannot execute command. Error: [string \"blah\"]:1: syntax error near ", - } + }, ], [ "2019-01-20 10:30:21 [LEAVE] User left the game", { - format: 'date', - time: '2019-01-20 10:30:21', - type: 'action', + format: "date", + time: "2019-01-20 10:30:21", + type: "action", action: "LEAVE", message: "User left the game", - } + }, ], [ "Error while running event level::on_nth_tick(1)", { - format: 'none', - type: 'generic', + format: "none", + type: "generic", message: "Error while running event level::on_nth_tick(1)", - } + }, ], [ "4802.763 Info AppManager.cpp:287: Saving to _autosave4 (blocking).", @@ -118,7 +119,7 @@ let testLines = new Map([ level: "Info", file: "AppManager.cpp:287", message: "Saving to _autosave4 (blocking).", - } + }, ], [ " 10.806 Info AppManager.cpp:394: Saving game as C:\\factorio\\saves\\world.zip", @@ -129,7 +130,7 @@ let testLines = new Map([ level: "Info", file: "AppManager.cpp:394", message: "Saving game as C:\\factorio\\saves\\world.zip", - } + }, ], [ "4202.780 Info AppManagerStates.cpp:1802: Saving finished", @@ -140,10 +141,10 @@ let testLines = new Map([ level: "Info", file: "AppManagerStates.cpp:1802", message: "Saving finished", - } + }, ], -]) +]); module.exports = { testLines, -} +}; diff --git a/test/lib/factorio/patch.js b/test/lib/factorio/patch.js index 79eeb422..02a2527b 100644 --- a/test/lib/factorio/patch.js +++ b/test/lib/factorio/patch.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const fs = require("fs-extra"); const jszip = require("jszip"); @@ -50,20 +51,20 @@ describe("lib/factorio", function() { describe("generateLoader()", function() { let reference = [ - '-- Auto generated scenario module loader created by Clusterio', - '-- Modifications to this file will be lost when loaded in Clusterio', - 'clusterio_patch_number = 1', - '', + "-- Auto generated scenario module loader created by Clusterio", + "-- Modifications to this file will be lost when loaded in Clusterio", + "clusterio_patch_number = 1", + "", 'local event_handler = require("event_handler")', - '', - '-- Scenario modules', + "", + "-- Scenario modules", 'event_handler.add_lib(require("foo"))', - '', - '-- Clusterio modules', + "", + "-- Clusterio modules", 'event_handler.add_lib(require("modules/spam/bar"))', 'require("modules/spam/spam")', - '', - ].join('\n'); + "", + ].join("\n"); let patchInfo = { "patch_number": 1, "scenario": { "modules": ["foo"] }, @@ -85,15 +86,15 @@ describe("lib/factorio", function() { describe("reorderDependencies()", function() { it("should reorder to satisfy simple dependency", function() { let modules = [ - { name: 'a', version: '1.0.0', dependencies: {'b': '*'} }, - { name: 'b', version: '1.0.0', dependencies: {} }, + { name: "a", version: "1.0.0", dependencies: {"b": "*"} }, + { name: "b", version: "1.0.0", dependencies: {} }, ]; factorio._reorderDependencies(modules); - assert(modules[0].name === 'b', "Dependency was not reorederd"); + assert(modules[0].name === "b", "Dependency was not reorederd"); }); it("should throw on invalid version", function() { let modules = [ - { name: 'a', version: 'foo', dependencies: {} }, + { name: "a", version: "foo", dependencies: {} }, ]; assert.throws( () => factorio._reorderDependencies(modules), @@ -102,7 +103,7 @@ describe("lib/factorio", function() { }); it("should throw on invalid version range", function() { let modules = [ - { name: 'a', version: '1.0.0', dependencies: {'b': 'invalid'} }, + { name: "a", version: "1.0.0", dependencies: {"b": "invalid"} }, ]; assert.throws( () => factorio._reorderDependencies(modules), @@ -111,16 +112,16 @@ describe("lib/factorio", function() { }); it("should throw on missing dependency", function() { let modules = [ - { name: 'a', version: '1.0.0', dependencies: {'b': '*'} }, + { name: "a", version: "1.0.0", dependencies: {"b": "*"} }, ]; assert.throws( () => factorio._reorderDependencies(modules), new Error("Missing dependency b for module a") ); modules = [ - { name: 'a', version: '1.0.0', dependencies: {'b': '*'} }, - { name: 'b', version: '1.0.0', dependencies: {'c': '*'} }, - { name: 'c', version: '1.0.0', dependencies: {'d': '*'} }, + { name: "a", version: "1.0.0", dependencies: {"b": "*"} }, + { name: "b", version: "1.0.0", dependencies: {"c": "*"} }, + { name: "c", version: "1.0.0", dependencies: {"d": "*"} }, ]; assert.throws( () => factorio._reorderDependencies(modules), @@ -129,8 +130,8 @@ describe("lib/factorio", function() { }); it("should throw on outdated dependency", function() { let modules = [ - { name: 'a', version: '1.0.0', dependencies: {} }, - { name: 'b', version: '1.0.0', dependencies: {'a': '>=2'} }, + { name: "a", version: "1.0.0", dependencies: {} }, + { name: "b", version: "1.0.0", dependencies: {"a": ">=2"} }, ]; assert.throws( () => factorio._reorderDependencies(modules), @@ -139,18 +140,18 @@ describe("lib/factorio", function() { }); it("should throw on dependency loops", function() { let modules = [ - { name: 'a', version: '1.0.0', dependencies: {'b': '*'} }, - { name: 'b', version: '1.0.0', dependencies: {'a': '*'} }, + { name: "a", version: "1.0.0", dependencies: {"b": "*"} }, + { name: "b", version: "1.0.0", dependencies: {"a": "*"} }, ]; assert.throws( () => factorio._reorderDependencies(modules), new Error("Module dependency loop detected: a -> b -> a") ); modules = [ - { name: 'd', version: '1.0.0', dependencies: {'b': '*'} }, - { name: 'a', version: '1.0.0', dependencies: {'b': '*'} }, - { name: 'b', version: '1.0.0', dependencies: {'c': '*'} }, - { name: 'c', version: '1.0.0', dependencies: {'a': '*'} }, + { name: "d", version: "1.0.0", dependencies: {"b": "*"} }, + { name: "a", version: "1.0.0", dependencies: {"b": "*"} }, + { name: "b", version: "1.0.0", dependencies: {"c": "*"} }, + { name: "c", version: "1.0.0", dependencies: {"a": "*"} }, ]; assert.throws( () => factorio._reorderDependencies(modules), @@ -170,7 +171,7 @@ describe("lib/factorio", function() { } } - let names = ['a', 'b', 'c']; + let names = ["a", "b", "c"]; let successCount = 0; const bitCount = names.length * (names.length - 1); for (let i=0; i < 2**bitCount; i++) { @@ -186,10 +187,10 @@ describe("lib/factorio", function() { let dependencies = {}; for (let k=0; k < names.length - 1; k++) { if (bits[k + j * (names.length - 1)]) { - dependencies[names[k + (k >= j)]] = '*'; + dependencies[names[k + (k >= j)]] = "*"; } } - modules.push({ name, version: '1.0.0', dependencies }); + modules.push({ name, version: "1.0.0", dependencies }); } let success = false; try { @@ -213,7 +214,7 @@ describe("lib/factorio", function() { let zip = new jszip(); zip.file("world/control.lua", "-- unknown\n"); let zipPath = path.join("temp", "test", "patch.zip"); - await fs.outputFile(zipPath, await zip.generateAsync({type: "nodebuffer"})) + await fs.outputFile(zipPath, await zip.generateAsync({ type: "nodebuffer" })); await assert.rejects( factorio.patch(zipPath, []), diff --git a/test/lib/factorio/server.js b/test/lib/factorio/server.js index a004f488..f42438e0 100644 --- a/test/lib/factorio/server.js +++ b/test/lib/factorio/server.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const events = require("events"); const fs = require("fs-extra"); @@ -84,7 +85,7 @@ describe("lib/factorio/server", function() { describe("randomDynamicPort()", function() { it("should return a port number", function() { - let port = factorio._randomDynamicPort() + let port = factorio._randomDynamicPort(); assert.equal(typeof port, "number"); assert(Number.isInteger(port)); assert(0 <= port && port < 2**16); @@ -92,7 +93,7 @@ describe("lib/factorio/server", function() { it("should return a port number in the dynamic range", function() { function validate(port) { - return 49152 <= port && port <= 65535; + return (49152 <= port && port <= 65535); } for (let i=0; i < 20; i++) { assert(validate(factorio._randomDynamicPort())); @@ -157,8 +158,8 @@ describe("lib/factorio/server", function() { describe("parseOutput()", function() { it("should parse the test lines", function() { for (let [line, reference] of testLines) { - reference.source = 'test'; - let output = factorio._parseOutput(line, 'test'); + reference.source = "test"; + let output = factorio._parseOutput(line, "test"); delete output.received; assert.deepEqual(output, reference); } @@ -187,50 +188,50 @@ describe("lib/factorio/server", function() { describe("._handleIpc()", function() { it("should emit the correct ipc event", async function() { - let waiter = events.once(server, 'ipc-channel'); + let waiter = events.once(server, "ipc-channel"); await server._handleIpc(Buffer.from('\f$ipc:channel?j"value"')); let result = await waiter; assert.equal(result[0], "value"); }); it("should handle special characters in channel name", async function() { - let waiter = events.once(server, 'ipc-$ ?\x00\x0a:'); + let waiter = events.once(server, "ipc-$ ?\x00\x0a:"); await server._handleIpc(Buffer.from('\f$ipc:$ \\x3f\\x00\\x0a:?j"value"')); let result = await waiter; assert.equal(result[0], "value"); }); it("should throw on malformed ipc line", async function() { await assert.rejects( - server._handleIpc(Buffer.from('\f$ipc:blah')), + server._handleIpc(Buffer.from("\f$ipc:blah")), new Error('Malformed IPC line "\f$ipc:blah"') ); - }) + }); it("should throw on unknown type", async function() { await assert.rejects( - server._handleIpc(Buffer.from('\f$ipc:channel??')), + server._handleIpc(Buffer.from("\f$ipc:channel??")), new Error("Unknown IPC type '?'") ); - }) + }); it("should throw on unknown file type", async function() { await assert.rejects( - server._handleIpc(Buffer.from('\f$ipc:channel?ffoo.invalid')), + server._handleIpc(Buffer.from("\f$ipc:channel?ffoo.invalid")), new Error("Unknown IPC file format 'invalid'") ); - }) + }); it("should throw on file name with slash", async function() { await assert.rejects( - server._handleIpc(Buffer.from('\f$ipc:channel?fa/b')), + server._handleIpc(Buffer.from("\f$ipc:channel?fa/b")), new Error("Invalid IPC file name 'a/b'") ); - }) + }); it("should load and delete json file", async function() { - let filePath = server.writePath('script-output', 'data.json'); + let filePath = server.writePath("script-output", "data.json"); await fs.outputFile(filePath, '{"data":"spam"}'); - let waiter = events.once(server, 'ipc-channel'); - await server._handleIpc(Buffer.from('\f$ipc:channel?fdata.json')); + let waiter = events.once(server, "ipc-channel"); + await server._handleIpc(Buffer.from("\f$ipc:channel?fdata.json")); let result = await waiter; assert.deepEqual(result[0], { "data": "spam" }); assert(!await fs.pathExists(filePath), "File was not deleted"); - }) + }); }); }); }); diff --git a/test/lib/fileOps/fileOps.js b/test/lib/fileOps/fileOps.js index d507b1ab..30744860 100644 --- a/test/lib/fileOps/fileOps.js +++ b/test/lib/fileOps/fileOps.js @@ -1,8 +1,9 @@ -const assert = require('assert').strict; -const fs = require('fs-extra'); -const path = require('path'); +"use strict"; +const assert = require("assert").strict; +const fs = require("fs-extra"); +const path = require("path"); -const fileOps = require('lib/fileOps'); +const fileOps = require("lib/fileOps"); describe("fileOps.js", function(){ let baseDir = path.join("temp", "test", "fileOps"); diff --git a/test/lib/generateSSLcert.js b/test/lib/generateSSLcert.js index 75f43b57..e64e6b43 100644 --- a/test/lib/generateSSLcert.js +++ b/test/lib/generateSSLcert.js @@ -1,10 +1,11 @@ +"use strict"; const assert = require("assert"); const fs = require("fs-extra"); const path = require("path"); -const generateSSLcert = require("lib/generateSSLcert") +const generateSSLcert = require("lib/generateSSLcert"); -describe("generateSSLcert.js(options)", ()=>{ +describe("generateSSLcert.js(options)", function() { let testDir = path.join("temp", "test", "certTest"); before(async function() { await fs.remove(testDir); @@ -12,6 +13,7 @@ describe("generateSSLcert.js(options)", ()=>{ it("Creates a folder with a .crt and .key file in it", async function() { this.timeout(1000); + // eslint-disable-next-line no-process-env if (process.env.FAST_TEST) { this.skip(); } diff --git a/test/lib/hash.js b/test/lib/hash.js index edcc5104..b12045d9 100644 --- a/test/lib/hash.js +++ b/test/lib/hash.js @@ -1,9 +1,10 @@ -const assert = require('assert').strict; -const fs = require('fs'); -const path = require('path'); -const stream = require('stream'); +"use strict"; +const assert = require("assert").strict; +const fs = require("fs"); +const path = require("path"); +const stream = require("stream"); -const hash = require('lib/hash'); +const hash = require("lib/hash"); describe("lib/hash", function() { diff --git a/test/lib/helpers.js b/test/lib/helpers.js index e5586bd5..cdad02ad 100644 --- a/test/lib/helpers.js +++ b/test/lib/helpers.js @@ -1,17 +1,18 @@ +"use strict"; const assert = require("assert").strict; const helpers = require("lib/helpers"); describe("lib/helpers", function() { - describe("basicType()", function() { - it("should return the expected values", function() { - assert.equal(helpers.basicType("s"), "string"); - assert.equal(helpers.basicType(null), "null"); - assert.equal(helpers.basicType(undefined), "undefined"); - assert.equal(helpers.basicType([1, 2]), "array"); - assert.equal(helpers.basicType(7), "number"); - assert.equal(helpers.basicType({ a: 1 }), "object"); - }); - }); + describe("basicType()", function() { + it("should return the expected values", function() { + assert.equal(helpers.basicType("s"), "string"); + assert.equal(helpers.basicType(null), "null"); + assert.equal(helpers.basicType(undefined), "undefined"); + assert.equal(helpers.basicType([1, 2]), "array"); + assert.equal(helpers.basicType(7), "number"); + assert.equal(helpers.basicType({ a: 1 }), "object"); + }); + }); }); diff --git a/test/lib/link/connector.js b/test/lib/link/connector.js index 6f0abb7c..4d650d67 100644 --- a/test/lib/link/connector.js +++ b/test/lib/link/connector.js @@ -1,8 +1,9 @@ -const assert = require('assert').strict; -const events = require('events'); +"use strict"; +const assert = require("assert").strict; +const events = require("events"); -const mock = require('../../mock'); -const link = require('lib/link'); +const mock = require("../../mock"); +const link = require("lib/link"); describe("lib/link/connectors", function() { @@ -33,7 +34,7 @@ describe("lib/link/connectors", function() { }); describe("class WebSocketClientConnector", function() { - let testConnector = new link.WebSocketClientConnector('url', 1); + let testConnector = new link.WebSocketClientConnector("url", 1); testConnector._socket = new mock.MockSocket(); describe(".register()", function() { @@ -49,10 +50,10 @@ describe("lib/link/connectors", function() { it("calls send on the socket", function() { testConnector._state = "handshake"; testConnector._socket.sentMessages = []; - testConnector.sendHandshake('test', { test: true }); + testConnector.sendHandshake("test", { test: true }); assert.deepEqual( testConnector._socket.sentMessages.map(JSON.parse), - [{ seq: null, type: 'test', data: { test: true }}] + [{ seq: null, type: "test", data: { test: true }}] ); }); }); @@ -61,10 +62,10 @@ describe("lib/link/connectors", function() { it("calls send on the socket", function() { testConnector._state = "connected"; testConnector._socket.sentMessages = []; - let seq = testConnector.send('test', { test: true }); + let seq = testConnector.send("test", { test: true }); assert.deepEqual( testConnector._socket.sentMessages.map(JSON.parse), - [{ seq, type: 'test', data: { test: true }}] + [{ seq, type: "test", data: { test: true }}] ); }); }); @@ -80,19 +81,19 @@ describe("lib/link/connectors", function() { describe(".processHandshake()", function() { it("should close on invalid message", function() { testConnector._socket.closeCalled = false; - testConnector._processHandshake({ data: "invalid message" }), + testConnector._processHandshake({ data: "invalid message" }); assert(testConnector._socket.closeCalled, "Close was not called on the socket"); }); it("should call register on hello", function() { let called = false; testConnector.register = () => { called = true; }; - testConnector._processHandshake({ seq: 1, type: 'hello', data: { version: "test", plugins: {} }}); + testConnector._processHandshake({ seq: 1, type: "hello", data: { version: "test", plugins: {} }}); assert(called, "register was not called"); }); it("should emit connect on ready", async function() { let result = events.once(testConnector, "connect"); testConnector._processHandshake( - { seq: 1, type: 'ready', data: { session_token: "x", heartbeat_interval: 10 }} + { seq: 1, type: "ready", data: { session_token: "x", heartbeat_interval: 10 }} ); await result; clearInterval(testConnector._heartbeatId); @@ -121,7 +122,7 @@ describe("lib/link/connectors", function() { it("should emit message on message in connected state", async function() { testConnector._socket.sentMessages = []; testConnector._state = "connected"; - let result = events.once(testConnector, 'message'); + let result = events.once(testConnector, "message"); testConnector._socket.events.get("message")('{"message":true}'); assert.deepEqual(await result, [{ message: true }]); }); diff --git a/test/lib/link/link.js b/test/lib/link/link.js index 2091305f..9b860673 100644 --- a/test/lib/link/link.js +++ b/test/lib/link/link.js @@ -1,5 +1,6 @@ -const assert = require('assert').strict; -const events = require('events'); +"use strict"; +const assert = require("assert").strict; +const events = require("events"); const link = require("lib/link"); const errors = require("lib/errors"); @@ -14,13 +15,13 @@ describe("lib/link/link", function() { before(function() { testConnector = new mock.MockConnector(); - testLink = new link.Link('source', 'target', testConnector); - testLink.setValidator('simple', schema.compile({ + testLink = new link.Link("source", "target", testConnector); + testLink.setValidator("simple", schema.compile({ properties: { "data": { - type: 'object', + type: "object", properties: { - "string": { type: 'string' }, + "string": { type: "string" }, }, }, }, @@ -28,20 +29,20 @@ describe("lib/link/link", function() { }); it("should emitt error processing message to connector", async function() { - testLink.setHandler('throws', () => { throw new Error("Handler error"); }, () => true); - let result = events.once(testConnector, 'message'); - testConnector.emit('message', { seq: 1, type: 'throws', data: {}}); + testLink.setHandler("throws", () => { throw new Error("Handler error"); }, () => true); + let result = events.once(testConnector, "message"); + testConnector.emit("message", { seq: 1, type: "throws", data: {}}); await assert.rejects(result, new Error("Handler error")); }); describe(".setValidator()", function() { it("should set the validator", function() { - testLink.setValidator('test', () => true); - assert(testLink._validators.has('test'), "validator was not added"); + testLink.setValidator("test", () => true); + assert(testLink._validators.has("test"), "validator was not added"); }); it("should throw on duplicated validator", function() { assert.throws( - () => testLink.setValidator('test', () => true), + () => testLink.setValidator("test", () => true), new Error("test already has a validator") ); }); @@ -50,18 +51,18 @@ describe("lib/link/link", function() { describe(".setHandler()", function() { it("should throw if validator is not passed", function() { assert.throws( - () => testLink.setHandler('no_validator_test', (message) => {}), + () => testLink.setHandler("no_validator_test", (message) => {}), new Error("validator is required") ); }); it("should set the handler", function() { - let handler = (message) => {}; - testLink.setHandler('handler_test', handler, (message) => true); - assert.equal(testLink._handlers.get('handler_test'), handler); + function handler(message) { } + testLink.setHandler("handler_test", handler, (message) => true); + assert.equal(testLink._handlers.get("handler_test"), handler); }); it("should throw on duplicated handler", function() { assert.throws( - () => testLink.setHandler('handler_test', (message) => {}), + () => testLink.setHandler("handler_test", (message) => {}), new Error("handler_test already has a handler") ); }); @@ -76,19 +77,19 @@ describe("lib/link/link", function() { }); it("should throw on message without validator", function() { assert.throws( - () => testLink.processMessage({ seq: 1, type: 'no_validator', data: {} }), + () => testLink.processMessage({ seq: 1, type: "no_validator", data: {} }), new errors.InvalidMessage("No validator for no_validator on source-target") ); }); it("should throw on message failing validation", function() { assert.throws( - () => testLink.processMessage({ seq: 1, type: 'simple', data: { string: 1 }}), + () => testLink.processMessage({ seq: 1, type: "simple", data: { string: 1 }}), { message: "Validation failed for simple" } ); }); it("should throw on unhandled message", function() { assert.throws( - () => testLink.processMessage({ seq: 1, type: 'simple', data: { string: "a" }}), + () => testLink.processMessage({ seq: 1, type: "simple", data: { string: "a" }}), new errors.InvalidMessage("Unhandled message simple") ); }); @@ -97,16 +98,16 @@ describe("lib/link/link", function() { describe("._processHandler()", function() { it("should call the handler", function() { let handled = []; - testLink.setHandler('handled', (message) => handled.push(message), (message) => true) - testLink._processHandler({ seq: 1, type: 'handled', data: { test: "foo" } }); - assert.deepEqual(handled, [{ seq: 1, type: 'handled', data: { test: "foo" } }]); + testLink.setHandler("handled", (message) => handled.push(message), (message) => true); + testLink._processHandler({ seq: 1, type: "handled", data: { test: "foo" } }); + assert.deepEqual(handled, [{ seq: 1, type: "handled", data: { test: "foo" } }]); }); it("should return true if handled", function() { - let result = testLink._processHandler({ seq: 1, type: 'handled', data: {} }); + let result = testLink._processHandler({ seq: 1, type: "handled", data: {} }); assert.equal(result, true); }); it("should return false if not handled", function() { - let result = testLink._processHandler({ seq: 1, type: 'not_handled', data: {} }); + let result = testLink._processHandler({ seq: 1, type: "not_handled", data: {} }); assert.equal(result, false); }); }); @@ -114,62 +115,62 @@ describe("lib/link/link", function() { describe("._processWaiters()", function() { it("should call waiters", async function() { let waiter = new Promise(resolve => { - testLink._waiters.set('test', [{ resolve, data: {} }]); + testLink._waiters.set("test", [{ resolve, data: {} }]); }); assert( - testLink._processWaiters({ type: 'test', seq: 4, data: {} }), + testLink._processWaiters({ type: "test", seq: 4, data: {} }), "Waiter was not processed" ); - assert.deepEqual(await waiter, { type: 'test', seq: 4, data: {} }); + assert.deepEqual(await waiter, { type: "test", seq: 4, data: {} }); }); it("should filter on data", async function() { let waiter = new Promise(resolve => { - testLink._waiters.set('test', [{ resolve, data: { a: 1, b: 2 } }]); + testLink._waiters.set("test", [{ resolve, data: { a: 1, b: 2 } }]); }); assert( - !testLink._processWaiters({ type: 'test', seq: 4, data: {} }), + !testLink._processWaiters({ type: "test", seq: 4, data: {} }), "Incorrectly matched empty message" ); assert( - !testLink._processWaiters({ type: 'test', seq: 4, data: { a: 1, b: 0 } }), + !testLink._processWaiters({ type: "test", seq: 4, data: { a: 1, b: 0 } }), "Incorrectly matched message with wrong data" ); assert( - testLink._processWaiters({ type: 'test', seq: 4, data: { a: 1, b: 2, c: 3 } }), + testLink._processWaiters({ type: "test", seq: 4, data: { a: 1, b: 2, c: 3 } }), "Did not match correct message" ); - assert.deepEqual(await waiter, { type: 'test', seq: 4, data: { a: 1, b: 2, c: 3 } }); + assert.deepEqual(await waiter, { type: "test", seq: 4, data: { a: 1, b: 2, c: 3 } }); }); }); describe(".waitFor()", function() { before(function() { - testLink.setValidator('waiter', (message) => true); + testLink.setValidator("waiter", (message) => true); }); it("should throw on missing validator", async function() { await assert.rejects( - testLink.waitFor('no_validator', {}), + testLink.waitFor("no_validator", {}), new Error("No validator for no_validator on source-target") ); }); it("should wait for a given message", async function() { - let result = testLink.waitFor('waiter', {}); + let result = testLink.waitFor("waiter", {}); assert( - testLink._processWaiters({ type: 'waiter', seq: 5, data: {} }), + testLink._processWaiters({ type: "waiter", seq: 5, data: {} }), "Did not proccess waiter" ); - assert.deepEqual(await result, { type: 'waiter', seq: 5, data: {} }); + assert.deepEqual(await result, { type: "waiter", seq: 5, data: {} }); }); it("should handle multiple waiters", async function() { - let result1 = testLink.waitFor('waiter', {}); - let result2 = testLink.waitFor('waiter', {}); + let result1 = testLink.waitFor("waiter", {}); + let result2 = testLink.waitFor("waiter", {}); assert( - testLink._processWaiters({ type: 'waiter', seq: 5, data: {} }), + testLink._processWaiters({ type: "waiter", seq: 5, data: {} }), "Did not proccess waiter" ); - assert.deepEqual(await result1, { type: 'waiter', seq: 5, data: {} }); - assert.deepEqual(await result2, { type: 'waiter', seq: 5, data: {} }); + assert.deepEqual(await result1, { type: "waiter", seq: 5, data: {} }); + assert.deepEqual(await result2, { type: "waiter", seq: 5, data: {} }); }); }); }); diff --git a/test/lib/link/messages.js b/test/lib/link/messages.js index f1e3ebf6..25817638 100644 --- a/test/lib/link/messages.js +++ b/test/lib/link/messages.js @@ -1,21 +1,22 @@ -const assert = require('assert').strict; +"use strict"; +const assert = require("assert").strict; -const link = require('lib/link'); -const errors = require('lib/errors'); -const mock = require('../../mock'); +const link = require("lib/link"); +const errors = require("lib/errors"); +const mock = require("../../mock"); describe("lib/link/messages", function() { - let testSourceLink = new link.Link('source', 'target', new mock.MockConnector()); - let testTargetLink = new link.Link('target', 'source', new mock.MockConnector()); + let testSourceLink = new link.Link("source", "target", new mock.MockConnector()); + let testTargetLink = new link.Link("target", "source", new mock.MockConnector()); let lastTargetSent; testTargetLink.send = (type, data) => { lastTargetSent = { type, data }; }; describe("class Request", function() { let testRequest = new link.Request({ - type: 'test', - links: ['source-target'], + type: "test", + links: ["source-target"], requestProperties: { "test": { type: "string" }, }, @@ -24,7 +25,7 @@ describe("lib/link/messages", function() { describe("constructor", function() { it("should throw on invalid forwardTo", function() { assert.throws( - () => new link.Request({ links: [], forwardTo: 'invalid'}), + () => new link.Request({ links: [], forwardTo: "invalid" }), new Error("Invalid forwardTo value invalid") ); }); @@ -33,7 +34,7 @@ describe("lib/link/messages", function() { describe(".attach()", function() { it("should attach validator to a source link", function() { testRequest.attach(testSourceLink); - assert(testSourceLink._validators.has('test_response'), "Validator was not set"); + assert(testSourceLink._validators.has("test_response"), "Validator was not set"); }); it("should throw if missing handler on target link", function() { assert.throws( @@ -44,17 +45,17 @@ describe("lib/link/messages", function() { let handlerResult; it("should attach handler to a target link", function() { - testRequest.attach(testTargetLink, async (message) => handlerResult ); - assert(testTargetLink._handlers.has('test_request'), "Handler was not set"); + testRequest.attach(testTargetLink, async (message) => handlerResult); + assert(testTargetLink._handlers.has("test_request"), "Handler was not set"); }); it("should send result of calling the handler", async function() { handlerResult = { test: "handler" }; - testTargetLink._handlers.get('test_request')({ seq: 2 }); + testTargetLink._handlers.get("test_request")({ seq: 2 }); let result = await new Promise(resolve => { testTargetLink.connector.send = (type, data) => resolve({ type, data }); }); assert.deepEqual(result, { - type: 'test_response', + type: "test_response", data: { test: "handler", seq: 2, @@ -63,12 +64,12 @@ describe("lib/link/messages", function() { }); it("should implicitly send an empty object on empty return", async function() { handlerResult = undefined; - testTargetLink._handlers.get('test_request')({ seq: 2 }); + testTargetLink._handlers.get("test_request")({ seq: 2 }); let result = await new Promise(resolve => { testTargetLink.connector.send = (type, data) => resolve({ type, data }); }); assert.deepEqual(result, { - type: 'test_response', + type: "test_response", data: { seq: 2, }, @@ -82,20 +83,16 @@ describe("lib/link/messages", function() { testSourceLink.connector.send = (type, data) => { request = { type, data }; }; - testSourceLink.waitFor = (type, condition) => { - return { data: { type, request }}; - }; + testSourceLink.waitFor = (type, condition) => ({ data: { type, request }}); assert.deepEqual( await testRequest.send(testSourceLink, { test: "request" }), - { type: 'test_response', request: { type: 'test_request', data: {test: "request" }}} + { type: "test_response", request: { type: "test_request", data: { test: "request" }}} ); delete testSourceLink.connector.send; delete testSourceLink.waitFor; }); it("should throw error response", async function() { - testSourceLink.waitFor = (type, condition) => { - return { data: { error: "test error" }}; - }; + testSourceLink.waitFor = (type, condition) => ({ data: { error: "test error" }}); await assert.rejects( testRequest.send(testSourceLink, { test: "blah" }), new errors.RequestError("test error") @@ -107,8 +104,8 @@ describe("lib/link/messages", function() { describe("class Event", function() { let testEvent = new link.Event({ - type: 'test', - links: ['source-target'], + type: "test", + links: ["source-target"], eventProperties: { "test": { type: "string" }, }, @@ -117,13 +114,13 @@ describe("lib/link/messages", function() { describe("constructor", function() { it("should throw on invalid forwardTo", function() { assert.throws( - () => new link.Event({ forwardTo: 'invalid'}), + () => new link.Event({ forwardTo: "invalid" }), new Error("Invalid forwardTo value invalid") ); }); it("should throw on invalid broadcastTo", function() { assert.throws( - () => new link.Event({ broadcastTo: 'invalid'}), + () => new link.Event({ broadcastTo: "invalid" }), new Error("Invalid broadcastTo value invalid") ); }); @@ -139,7 +136,7 @@ describe("lib/link/messages", function() { let called = false; it("should attach handler and validator to a target link", function() { testEvent.attach(testTargetLink, async (message) => { called = true; }); - assert(testTargetLink._validators.has('test_event'), "Validator was not set"); + assert(testTargetLink._validators.has("test_event"), "Validator was not set"); }); it("should call the attached event handler", function() { testTargetLink._handlers.get("test_event")(); @@ -163,6 +160,6 @@ describe("lib/link/messages", function() { describe("attachAllMessages()", function() { it("does not throw", function() { link.attachAllMessages(testSourceLink); - }) + }); }); }); diff --git a/test/lib/luaTools.js b/test/lib/luaTools.js index c1dd8d36..c2958db1 100644 --- a/test/lib/luaTools.js +++ b/test/lib/luaTools.js @@ -1,6 +1,7 @@ -const assert = require('assert').strict; +"use strict"; +const assert = require("assert").strict; -const luaTools = require('lib/luaTools'); +const luaTools = require("lib/luaTools"); describe("lib/luaTools", function() { diff --git a/test/lib/objectOps.js b/test/lib/objectOps.js index 5e591776..8ce2a769 100644 --- a/test/lib/objectOps.js +++ b/test/lib/objectOps.js @@ -1,32 +1,14 @@ -var assert = require('assert'); -var objectOps = require('lib/objectOps'); +"use strict"; +var assert = require("assert"); +var objectOps = require("lib/objectOps"); describe("objectOps.js", function(){ - /*describe("objectOps.clone()", function(){ - it("clones javascript objects", function(){ - var object1 = {hello:"world", cat:{legs:4, name:"Kitty", colors:["brown", "yellow", "purple"]}}; - var object2 = {}; - object2 = objectOps.clone(object1); - - assert.equal(object2.cat.name, object1.cat.name) - assert.equal(object1.hello, object2.hello) - }); - it("does not deep clone objects", function(){ - var obj1 = {hello:"world", cat:{legs:4, name:"Kitty", colors:["brown", "yellow", "purple"]}}; - var obj2 = {}; - obj2 = objectOps.clone(obj1); - - obj1.cat.colors = "black"; - assert.equal(obj1.cat.colors, "black"); - assert.notEqual(obj2.cat.colors[1], "yellow"); - }); - });*/ describe("objectOps.deepclone()", function(){ it("deep clones javascript objects", function(){ - var obj1 = {hello:"world", cat:{legs:4, name:"Kitty", colors:["brown", "yellow", "purple"]}}; + var obj1 = { hello: "world", cat: { legs: 4, name: "Kitty", colors: ["brown", "yellow", "purple"] }}; var obj2 = {}; obj2 = objectOps.deepclone(obj1); - + obj1.cat.colors = "black"; assert.equal(obj1.cat.colors, "black"); assert.equal(obj2.cat.colors[1], "yellow"); @@ -37,4 +19,4 @@ describe("objectOps.js", function(){ }); }); }); -}); \ No newline at end of file +}); diff --git a/test/lib/plugin.js b/test/lib/plugin.js index 3e147065..f1122edc 100644 --- a/test/lib/plugin.js +++ b/test/lib/plugin.js @@ -1,11 +1,12 @@ -const assert = require('assert').strict; -const fs = require('fs-extra'); -const path = require('path'); +"use strict"; +const assert = require("assert").strict; +const fs = require("fs-extra"); +const path = require("path"); -const mock = require('../mock'); -const link = require('lib/link'); -const plugin = require('lib/plugin'); -const errors = require('lib/errors'); +const mock = require("../mock"); +const link = require("lib/link"); +const plugin = require("lib/plugin"); +const errors = require("lib/errors"); describe("lib/plugin", function() { @@ -14,7 +15,7 @@ describe("lib/plugin", function() { it("should be constructible", async function() { instancePlugin = new plugin.BaseInstancePlugin(); await instancePlugin.init(); - }) + }); it("should define defaults for hooks", async function() { await instancePlugin.onMetrics(); await instancePlugin.onStart(); @@ -23,7 +24,7 @@ describe("lib/plugin", function() { await instancePlugin.onOutput({}); instancePlugin.onMasterConnectionEvent("connect"); await instancePlugin.onPrepareMasterDisconnect(); - }) + }); }); describe("class BaseMasterPlugin", function() { @@ -31,38 +32,38 @@ describe("lib/plugin", function() { it("should be constructible", async function() { masterPlugin = new plugin.BaseMasterPlugin(); await masterPlugin.init(); - }) + }); it("should define defaults for hooks", async function() { await masterPlugin.onInstanceStatusChanged({}, "running", "initialized"); await masterPlugin.onMetrics(); await masterPlugin.onShutdown(); masterPlugin.onSlaveConnectionEvent({}, "connect"); await masterPlugin.onPrepareSlaveDisconnect({}); - }) + }); }); describe("loadPluginInfos()", function() { let baseDir = path.join("temp", "test", "plugin"); - let emptyDir = path.join(baseDir, 'emptyDir'); - let emptyPlugin = path.join(baseDir, 'emptyPlugin'); - let testPlugin = path.join(baseDir, 'testPlugin'); - let brokenPlugin = path.join(baseDir, 'brokenPlugin'); - let invalidPlugin = path.join(baseDir, 'invalidPlugin'); + let emptyDir = path.join(baseDir, "emptyDir"); + let emptyPlugin = path.join(baseDir, "emptyPlugin"); + let testPlugin = path.join(baseDir, "testPlugin"); + let brokenPlugin = path.join(baseDir, "brokenPlugin"); + let invalidPlugin = path.join(baseDir, "invalidPlugin"); before(async function() { await fs.ensureDir(emptyDir); - await fs.ensureDir(path.join(emptyPlugin, 'empty')); + await fs.ensureDir(path.join(emptyPlugin, "empty")); async function writePlugin(pluginPath, name, infoName = name) { await fs.outputFile( - path.join(pluginPath, name, 'info.js'), + path.join(pluginPath, name, "info.js"), `module.exports = { name: "${infoName}" };` ); } - await writePlugin(testPlugin, 'test'); - await writePlugin(brokenPlugin, 'broken'); - await fs.outputFile(path.join(brokenPlugin, 'broken', 'info.js'), "Syntax Error"); - await writePlugin(invalidPlugin, 'invalid', 'wrong'); + await writePlugin(testPlugin, "test"); + await writePlugin(brokenPlugin, "broken"); + await fs.outputFile(path.join(brokenPlugin, "broken", "info.js"), "Syntax Error"); + await writePlugin(invalidPlugin, "invalid", "wrong"); }); it("should return an empty array for an empty directory", async function() { @@ -74,7 +75,7 @@ describe("lib/plugin", function() { it("should discover test plugin", async function() { assert.deepEqual( await plugin.loadPluginInfos(testPlugin), - [{ name: 'test' }] + [{ name: "test" }] ); }); it("should reject on broken plugin", async function() { @@ -91,18 +92,18 @@ describe("lib/plugin", function() { }); after(async function() { - await fs.unlink(path.join(brokenPlugin, 'broken', 'info.js')); + await fs.unlink(path.join(brokenPlugin, "broken", "info.js")); }); }); describe("attachPluginMessages()", function() { - let mockLink = new link.Link('source', 'target', new mock.MockConnector()); + let mockLink = new link.Link("source", "target", new mock.MockConnector()); let mockEvent = new link.Event({ type: "test:test", links: ["target-source"] }); it("should accept pluginInfo without messages", function() { plugin.attachPluginMessages(mockLink, {}, null); }); it("should attach handler for the given message", function() { - let mockEventEventHandler = function() {}; + function mockEventEventHandler() { }; plugin.attachPluginMessages(mockLink, { name: "test", messages: { mockEvent }}, { mockEventEventHandler }); assert(mockLink._handlers.get("test:test_event"), "handler was not registered"); }); diff --git a/test/lib/prometheus.js b/test/lib/prometheus.js index f959556b..d1602c47 100644 --- a/test/lib/prometheus.js +++ b/test/lib/prometheus.js @@ -1,6 +1,7 @@ -const assert = require('assert').strict; +"use strict"; +const assert = require("assert").strict; -const prometheus = require('lib/prometheus'); +const prometheus = require("lib/prometheus"); describe("lib/prometheus", function() { @@ -8,7 +9,7 @@ describe("lib/prometheus", function() { let registry; beforeEach(function() { registry = new prometheus.CollectorRegistry(); - }) + }); describe(".register()", function() { it("should register a collector", function() { @@ -105,22 +106,22 @@ describe("lib/prometheus", function() { describe("class Counter", function() { let counter; beforeEach(function() { - counter = new prometheus.Counter('test', "Help", { register: false }); + counter = new prometheus.Counter("test", "Help", { register: false }); }); describe("constructor", function() { it("should be initialized to 0", function() { - assert.equal(counter._values.get(''), 0); + assert.equal(counter._values.get(""), 0); }); }); describe(".inc()", function() { it("should increment the counter by 1 by default", function() { counter.inc(); - assert.equal(counter._values.get(''), 1); + assert.equal(counter._values.get(""), 1); }); it("should increment the counter by specified value", function() { counter.inc(3.2); - assert.equal(counter._values.get(''), 3.2); + assert.equal(counter._values.get(""), 3.2); }); it("should throw if value is negative", function() { assert.throws( @@ -146,53 +147,53 @@ describe("lib/prometheus", function() { describe("class Gauge", function() { let gauge; beforeEach(function() { - gauge = new prometheus.Gauge('test', "Help", { register: false }); + gauge = new prometheus.Gauge("test", "Help", { register: false }); }); describe("constructor", function() { it("should be initialized to 0", function() { - assert.equal(gauge._values.get(''), 0); + assert.equal(gauge._values.get(""), 0); }); }); describe(".inc()", function() { it("should increment the gauge by 1 by default", function() { gauge.inc(); - assert.equal(gauge._values.get(''), 1); + assert.equal(gauge._values.get(""), 1); }); it("should increment the gauge by specified value", function() { gauge.inc(3.2); - assert.equal(gauge._values.get(''), 3.2); + assert.equal(gauge._values.get(""), 3.2); }); it("should decrement on negative value", function() { gauge.inc(-2); - assert.equal(gauge._values.get(''), -2); + assert.equal(gauge._values.get(""), -2); }); }); describe(".dec()", function() { it("should decrement the gauge by 1 by default", function() { gauge.dec(); - assert.equal(gauge._values.get(''), -1); + assert.equal(gauge._values.get(""), -1); }); it("should decrement the gauge by specified value", function() { gauge.dec(3.2); - assert.equal(gauge._values.get(''), -3.2); + assert.equal(gauge._values.get(""), -3.2); }); it("should increment on negative value", function() { gauge.dec(-2); - assert.equal(gauge._values.get(''), 2); + assert.equal(gauge._values.get(""), 2); }); }); describe(".set()", function() { it("should set the gauge to the specified value", function() { gauge.set(7); - assert.equal(gauge._values.get(''), 7); + assert.equal(gauge._values.get(""), 7); }); }); describe(".setToCurrentTime()", function() { it("should set the gauge to current unix time in seconds", function() { gauge.setToCurrentTime(); assert( - Math.abs(gauge._values.get('') - Date.now() / 1000) < 1, + Math.abs(gauge._values.get("") - Date.now() / 1000) < 1, "value set is not close to the current unix time" ); }); @@ -208,17 +209,17 @@ describe("lib/prometheus", function() { name, "Help", { register: false } ), new Error(`Invalid name '${name}'`) - ) + ); } }); it("should throw on invalid label names", function() { for (let name of ["", " ", "0a", "$a", "a:"]) { assert.throws( () => new prometheus.Gauge( - 'test', "Help", { register: false, labels: [name] } + "test", "Help", { register: false, labels: [name] } ), new Error(`Invalid label '${name}'`) - ) + ); } }); it("should throw on missing name", function() { @@ -243,7 +244,7 @@ describe("lib/prometheus", function() { describe(".get()", function() { it("should return the current value", function() { - let gauge = new prometheus.Gauge('test', "Help", { register: false }); + let gauge = new prometheus.Gauge("test", "Help", { register: false }); gauge.set(11); assert.equal(gauge.get(), 11); }); @@ -253,34 +254,34 @@ describe("lib/prometheus", function() { let gauge; beforeEach(function() { gauge = new prometheus.Gauge( - 'test', "Help", { register: false, labels: ['a', 'b'] } + "test", "Help", { register: false, labels: ["a", "b"] } ); }); it("should require all labels set at creation", function() { assert.throws( - () => gauge.labels({a: "1"}), + () => gauge.labels({ a: "1" }), new Error("Missing label 'b'") ); assert.throws( - () => gauge.labels({b: "1"}), + () => gauge.labels({ b: "1" }), new Error("Missing label 'a'") ); assert.throws( - () => gauge.labels({a: '1', b: '2', c: '3'}), + () => gauge.labels({ a: "1", b: "2", c: "3" }), new Error("Extra label 'c'") ); assert.throws( - () => gauge.labels('1'), + () => gauge.labels("1"), new Error("Missing label 'b'") ); assert.throws( - () => gauge.labels('1', '2', '3'), + () => gauge.labels("1", "2", "3"), new Error("Extra positional label") ); }); it("should throw if label value is not a string", function() { assert.throws( - () => gauge.labels({a: "1", b: 2}), + () => gauge.labels({ a: "1", b: 2 }), new Error("Expected value for label 'b' to be a string") ); assert.throws( @@ -289,39 +290,39 @@ describe("lib/prometheus", function() { ); }); it("should initialize a value for the label set", function() { - gauge.labels({ a: '1', b: '2'}); + gauge.labels({ a: "1", b: "2" }); assert(gauge._values.has('a="1",b="2"'), "value was not set"); - gauge.labels('4', '5'); + gauge.labels("4", "5"); assert(gauge._values.has('a="4",b="5"'), "value was not set"); }); it("should return a child supporting the usual methods", function() { - let child = gauge.labels({ a: '1', b: '2'}); + let child = gauge.labels({ a: "1", b: "2" }); child.get(); child.inc(); child.dec(); child.setToCurrentTime(); - child = gauge.labels('7', '8'); + child = gauge.labels("7", "8"); child.get(); child.inc(); child.dec(); child.setToCurrentTime(); }); it("should return the same child for the same labels", function() { - let child = gauge.labels('1', '2'); - assert.equal(gauge.labels('1', '2'), child); + let child = gauge.labels("1", "2"); + assert.equal(gauge.labels("1", "2"), child); }); }); describe(".remove()", function() { it("should remove a label set created with .labels()", function() { let gauge = new prometheus.Gauge( - 'test', "Help", { register: false, labels: ['a', 'b'] } + "test", "Help", { register: false, labels: ["a", "b"] } ); - gauge.labels({ a: '1', b: '2'}); - gauge.remove({ a: '1', b: '2'}); + gauge.labels({ a: "1", b: "2" }); + gauge.remove({ a: "1", b: "2" }); assert(!gauge._values.has('a="1",b="2"'), "value was not removed"); }); it("should throw when removing the empty label set", function() { - let gauge = new prometheus.Gauge('test', "Help", { register: false }); + let gauge = new prometheus.Gauge("test", "Help", { register: false }); assert.throws( () => gauge.remove({}), new Error("labels cannot be empty") ); @@ -330,46 +331,46 @@ describe("lib/prometheus", function() { describe(".removeAll()", function() { it("should remove a label set created with .labels()", function() { let gauge = new prometheus.Gauge( - 'test', "Help", { register: false, labels: ['a', 'b'] } + "test", "Help", { register: false, labels: ["a", "b"] } ); - gauge.labels('1', '2'); - gauge.labels('1', '3'); - gauge.labels('4', '6'); - gauge.removeAll({a: '1'}); - assert(gauge._values.size <= 1, "Matching labels remained") + gauge.labels("1", "2"); + gauge.labels("1", "3"); + gauge.labels("4", "6"); + gauge.removeAll({ a: "1" }); + assert(gauge._values.size <= 1, "Matching labels remained"); assert( gauge._values.has('a="4",b="6"'), "Non-matching label was removed" ); }); it("should throw when removing the empty label set", function() { - let gauge = new prometheus.Gauge('test', "Help", { register: false }); + let gauge = new prometheus.Gauge("test", "Help", { register: false }); assert.throws( () => gauge.removeAll({}), new Error("labels cannot be empty") ); }); it("should handle special characters", function() { let gauge = new prometheus.Gauge( - 'test', "Help", { register: false, labels: ['a', 'b'] } - ) - gauge.labels('x\\\\\n\"\"\n\\', '2'); - gauge.removeAll({a: 'x\\\\\n\"\"\n\\'}); - assert(gauge._values.size === 0, "Matching labels remained") + "test", "Help", { register: false, labels: ["a", "b"] } + ); + gauge.labels('x\\\\\n\"\"\n\\', "2"); + gauge.removeAll({ a: 'x\\\\\n\"\"\n\\' }); + assert(gauge._values.size === 0, "Matching labels remained"); }); }); describe(".clear()", function() { it("should remove all label sets created with .labels()", function() { let gauge = new prometheus.Gauge( - 'test', "Help", { register: false, labels: ['a', 'b'] } + "test", "Help", { register: false, labels: ["a", "b"] } ); - gauge.labels({ a: '1', b: '2'}); - gauge.labels({ a: '3', b: '4'}); - gauge.labels({ a: '5', b: '6'}); + gauge.labels({ a: "1", b: "2" }); + gauge.labels({ a: "3", b: "4" }); + gauge.labels({ a: "5", b: "6" }); gauge.clear(); assert(gauge._values.size == 0, "values was not cleared"); }); it("should throw when used on an unlabled metric", function() { - let gauge = new prometheus.Gauge('test', "Help", { register: false }); + let gauge = new prometheus.Gauge("test", "Help", { register: false }); assert.throws( () => gauge.clear(), new Error("Cannot clear unlabeled metric") ); @@ -381,7 +382,7 @@ describe("lib/prometheus", function() { describe("processStartTimeSeconds", function() { it("should give a close point in time", async function() { this.skip(); // XXX Doesn't work in Clusterio's shared testing env - let results = [] + let results = []; let collector = prometheus.defaultCollectors.processStartTimeSeconds; for await (let result of collector.collect()) { results.push(result); @@ -393,7 +394,7 @@ describe("lib/prometheus", function() { ); assert( - Math.abs(results[0].samples.get('') - Date.now() / 1000) < 1, + Math.abs(results[0].samples.get("") - Date.now() / 1000) < 1, "value set is not close to the current unix time" ); }); @@ -401,7 +402,7 @@ describe("lib/prometheus", function() { describe("processCpuSecondsTotal", function() { it("should give a low value", async function() { this.skip(); // XXX Doesn't work in Clusterio's shared testing env - let results = [] + let results = []; let collector = prometheus.defaultCollectors.processCpuSecondsTotal; for await (let result of collector.collect()) { results.push(result); @@ -412,7 +413,7 @@ describe("lib/prometheus", function() { "collector did not give exactly one result" ); - let value = results[0].samples.get('') + let value = results[0].samples.get(""); assert( value < 5 && value > 0, `process time is not between 0 and 5 seconds (${value})` @@ -422,7 +423,7 @@ describe("lib/prometheus", function() { describe("processResidentMemoryBytes", function() { it("should give a low value", async function() { this.skip(); // XXX Doesn't work in Clusterio's shared testing env - let results = [] + let results = []; let collector = prometheus.defaultCollectors.processResidentMemoryBytes; for await (let result of collector.collect()) { results.push(result); @@ -433,7 +434,7 @@ describe("lib/prometheus", function() { "collector did not give exactly one result" ); - let value = results[0].samples.get('') + let value = results[0].samples.get(""); assert( value < 200e6 && value > 0, `resident memory is not between 0 and 200 MB (${value})` @@ -443,7 +444,7 @@ describe("lib/prometheus", function() { describe("processHeapBytes", function() { it("should give a low value", async function() { this.skip(); // XXX Doesn't work in Clusterio's shared testing env - let results = [] + let results = []; let collector = prometheus.defaultCollectors.processHeapBytes; for await (let result of collector.collect()) { results.push(result); @@ -454,7 +455,7 @@ describe("lib/prometheus", function() { "collector did not give exactly one result" ); - let value = results[0].samples.get('') + let value = results[0].samples.get(""); assert( value < 200e6 && value > 0, `heap is not between 0 and 200 MB (${value})` @@ -466,35 +467,35 @@ describe("lib/prometheus", function() { describe("exposition()", function() { it("should format simple metrics", async function() { let registry = new prometheus.CollectorRegistry(); - let counter = new prometheus.Counter('a', "Help", { register: false }); + let counter = new prometheus.Counter("a", "Help", { register: false }); registry.register(counter); - let gauge = new prometheus.Gauge('b', "Help", { register: false }); + let gauge = new prometheus.Gauge("b", "Help", { register: false }); registry.register(gauge); let exposition = await prometheus.exposition(registry.collect()); assert.equal( exposition, - '# HELP a Help\n' + - '# TYPE a counter\n' + - 'a 0\n' + - '\n' + - '# HELP b Help\n' + - '# TYPE b gauge\n' + - 'b 0\n' + "# HELP a Help\n" + + "# TYPE a counter\n" + + "a 0\n" + + "\n" + + "# HELP b Help\n" + + "# TYPE b gauge\n" + + "b 0\n" ); }); it("should format labeled metrics", async function() { let registry = new prometheus.CollectorRegistry(); let gauge = new prometheus.Gauge( - 'test', "Help", { register: false, labels: ['a', 'b']} + "test", "Help", { register: false, labels: ["a", "b"] } ); - gauge.labels({a: '1', b: '2'}); - gauge.labels({b: 'y', a: 'x'}); + gauge.labels({ a: "1", b: "2" }); + gauge.labels({ b: "y", a: "x" }); registry.register(gauge); let exposition = await prometheus.exposition(registry.collect()); assert.equal( exposition, - '# HELP test Help\n' + - '# TYPE test gauge\n' + + "# HELP test Help\n" + + "# TYPE test gauge\n" + 'test{a="1",b="2"} 0\n' + 'test{a="x",b="y"} 0\n' ); @@ -502,17 +503,17 @@ describe("lib/prometheus", function() { it("should format Infinity and NaN", async function() { let registry = new prometheus.CollectorRegistry(); let gauge = new prometheus.Gauge( - 'test', "Help", { register: false, labels: ['n']} + "test", "Help", { register: false, labels: ["n"] } ); - gauge.labels({n: '1'}).set(Infinity); - gauge.labels({n: '2'}).set(-Infinity); - gauge.labels({n: '3'}).set(NaN); + gauge.labels({ n: "1" }).set(Infinity); + gauge.labels({ n: "2" }).set(-Infinity); + gauge.labels({ n: "3" }).set(NaN); registry.register(gauge); let exposition = await prometheus.exposition(registry.collect()); assert.equal( exposition, - '# HELP test Help\n' + - '# TYPE test gauge\n' + + "# HELP test Help\n" + + "# TYPE test gauge\n" + 'test{n="1"} +Inf\n' + 'test{n="2"} -Inf\n' + 'test{n="3"} NaN\n' @@ -521,11 +522,11 @@ describe("lib/prometheus", function() { }); let serializedResult = { - metric: { type: 'type', name: 'name', help: "Help", labels: ['a', 'b'] }, - samples: [ ['a="1",b="2"', 1], ['a="3",b="4"', 5], ] + metric: { type: "type", name: "name", help: "Help", labels: ["a", "b"] }, + samples: [['a="1",b="2"', 1], ['a="3",b="4"', 5]], }; let deserializedResult = { - metric: new prometheus.Metric('type', 'name', "Help", ['a', 'b']), + metric: new prometheus.Metric("type", "name", "Help", ["a", "b"]), samples: new Map([['a="1",b="2"', 1], ['a="3",b="4"', 5]]), }; describe("serializeResult()", function() { @@ -533,26 +534,26 @@ describe("lib/prometheus", function() { assert.deepEqual( prometheus.serializeResult(deserializedResult), serializedResult - ) + ); }); it("should support adding labels", function() { assert.deepEqual( prometheus.serializeResult({ - metric: new prometheus.Metric('type', 'name', "Help"), - samples: new Map([['', 2]]), - }, { addLabels: { c: '8' }}), + metric: new prometheus.Metric("type", "name", "Help"), + samples: new Map([["", 2]]), + }, { addLabels: { c: "8" }}), { - metric: {type: 'type', name: 'name', help: "Help", labels: ['c']}, + metric: { type: "type", name: "name", help: "Help", labels: ["c"] }, samples: [['c="8"', 2]], } ); assert.deepEqual( prometheus.serializeResult({ - metric: new prometheus.Metric('type', 'name', "Help", ['a']), + metric: new prometheus.Metric("type", "name", "Help", ["a"]), samples: new Map([['a="4"', 3]]), - }, { addLabels: { c: '8' }}), + }, { addLabels: { c: "8" }}), { - metric: {type: 'type', name: 'name', help: "Help", labels: ['a', 'c']}, + metric: { type: "type", name: "name", help: "Help", labels: ["a", "c"] }, samples: [['a="4",c="8"', 3]], } ); @@ -560,30 +561,30 @@ describe("lib/prometheus", function() { it("should support redefining name and help", function() { assert.deepEqual( prometheus.serializeResult({ - metric: new prometheus.Metric('type', 'name', "Help"), - samples: new Map([['', 2]]), - }, { metricName: 'new', }), + metric: new prometheus.Metric("type", "name", "Help"), + samples: new Map([["", 2]]), + }, { metricName: "new" }), { - metric: {type: 'type', name: 'new', help: "Help", labels: []}, - samples: [['', 2]], + metric: { type: "type", name: "new", help: "Help", labels: [] }, + samples: [["", 2]], } ); assert.deepEqual( prometheus.serializeResult({ - metric: new prometheus.Metric('type', 'name', "Help"), - samples: new Map([['', 2]]), - }, { metricHelp: "old", }), + metric: new prometheus.Metric("type", "name", "Help"), + samples: new Map([["", 2]]), + }, { metricHelp: "old" }), { - metric: {type: 'type', name: 'name', help: "old", labels: []}, - samples: [['', 2]], + metric: { type: "type", name: "name", help: "old", labels: [] }, + samples: [["", 2]], } ); }); it("should throw on unrecoginzed option", function() { assert.throws( () => new prometheus.serializeResult({ - metric: new prometheus.Metric('type', 'name', "Help"), - samples: new Map([['', 3]]), + metric: new prometheus.Metric("type", "name", "Help"), + samples: new Map([["", 3]]), }, { invalid: true }), new Error("Unrecognized option 'invalid'") ); @@ -595,7 +596,7 @@ describe("lib/prometheus", function() { assert.deepEqual( prometheus.deserializeResult(serializedResult), deserializedResult - ) + ); }); }); diff --git a/test/lib/users.js b/test/lib/users.js index 09c37f16..901bb53b 100644 --- a/test/lib/users.js +++ b/test/lib/users.js @@ -1,3 +1,4 @@ +"use strict"; const assert = require("assert").strict; const users = require("lib/users"); diff --git a/test/master.js b/test/master.js index 0c985e0a..128f106e 100644 --- a/test/master.js +++ b/test/master.js @@ -1,8 +1,9 @@ -const assert = require('assert').strict; +"use strict"; +const assert = require("assert").strict; -const mock = require('./mock'); -const master = require('../master'); +const mock = require("./mock"); +const master = require("../master"); -describe('Master testing', function() { +describe("Master testing", function() { }); diff --git a/test/missing.js b/test/missing.js index 35e53b8d..4cdbc87c 100644 --- a/test/missing.js +++ b/test/missing.js @@ -1,5 +1,6 @@ // These are libraries that does not have any units test, they are // required here in order to track coverage for them. -require('lib/manager/modManager'); -require('lib/npmPostinstall.js'); +"use strict"; +require("lib/manager/modManager"); +require("lib/npmPostinstall.js"); diff --git a/test/mock.js b/test/mock.js index f2354628..c919d57c 100644 --- a/test/mock.js +++ b/test/mock.js @@ -1,3 +1,4 @@ +"use strict"; const events = require("events"); const link = require("lib/link"); @@ -42,7 +43,7 @@ class MockConnector extends events.EventEmitter { send(type, data) { let message = { seq: this._seq, type, data }; this.sentMessages.push(message); - setImmediate(() => this.emit('send', message)); + setImmediate(() => this.emit("send", message)); return this._seq++; } } @@ -55,27 +56,27 @@ class MockServer { async sendRcon(command) { this.rconCommands.push(command); - return this.rconCommandResults.get(command) || ''; + return this.rconCommandResults.get(command) || ""; } } class MockInstance extends link.Link { constructor() { - super('instance', 'slave', new MockConnector()); + super("instance", "slave", new MockConnector()); this.server = new MockServer(); this.name = "test"; this.config = { get: (name) => { - if (name === "instance.id") { return 7357 } + if (name === "instance.id") { return 7357; } throw Error("Not implemented"); - } - } + }, + }; } } class MockSlave extends link.Link { constructor() { - super('slave', 'master', new MockConnector()); + super("slave", "master", new MockConnector()); } } diff --git a/test/slave.js b/test/slave.js index 1ce8ed1c..159d310a 100644 --- a/test/slave.js +++ b/test/slave.js @@ -1,6 +1,7 @@ -const assert = require('assert').strict; -const path = require('path'); -const fs = require('fs-extra'); +"use strict"; +const assert = require("assert").strict; +const path = require("path"); +const fs = require("fs-extra"); const link = require("lib/link"); const config = require("lib/config"); @@ -23,16 +24,16 @@ describe("Slave testing", function() { describe(".name", function() { it("should give the name of the instance", function() { assert.equal(instance.name, "foo"); - }) + }); }); describe(".path()", function() { it("should give the path when called without arguments", function() { assert.equal(instance.path(), "dir"); - }) + }); it("should join path with arguments", function() { assert.equal(instance.path("bar"), path.join("dir", "bar")); - }) + }); }); }); @@ -65,19 +66,19 @@ describe("Slave testing", function() { }); it("should throw on CON, PRN, AUX, NUL, COM1, LPT1", function() { - for (let bad of ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'LPT1']) { + for (let bad of ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"]) { check( - bad, 'cannot be named any of . ..' - +' CON PRN AUX NUL COM1-9 and LPT1-9' + bad, "cannot be named any of . .." + +" CON PRN AUX NUL COM1-9 and LPT1-9" ); } }); it("should throw on . and ..", function() { - for (let bad of ['.', '..']) { + for (let bad of [".", ".."]) { check( - bad, 'cannot be named any of . ..' - +' CON PRN AUX NUL COM1-9 and LPT1-9' + bad, "cannot be named any of . .." + +" CON PRN AUX NUL COM1-9 and LPT1-9" ); } }); @@ -93,7 +94,7 @@ describe("Slave testing", function() { let discardingLogger = { warning: function() { }, log: function() { }, - } + }; let instance; before(async function() {