mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
84 files changed
+1758
-1348
No files matched your search
+253
@@ -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 }],
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
+9
-9
@@ -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 = {
|
||||
|
||||
+56
-53
@@ -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 <command> [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) {
|
||||
|
||||
+15
-10
@@ -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.
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"clusterctl.js",
|
||||
"lib/",
|
||||
"plugins/",
|
||||
"routes/",
|
||||
"routes.js"
|
||||
],
|
||||
"excludePattern": "node_modules"
|
||||
|
||||
+76
-38
@@ -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,
|
||||
}
|
||||
};
|
||||
@@ -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<Object>} 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,
|
||||
}
|
||||
};
|
||||
+2
-1
@@ -3,7 +3,8 @@
|
||||
* @author Hornwitser
|
||||
* @module lib/config
|
||||
*/
|
||||
"use strict";
|
||||
module.exports = {
|
||||
...require("./classes"),
|
||||
...require("./definitions"),
|
||||
}
|
||||
};
|
||||
+5
-6
@@ -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,
|
||||
}
|
||||
};
|
||||
+2
-1
@@ -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,
|
||||
}
|
||||
};
|
||||
+11
-6
@@ -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<string, string>} modVersions -
|
||||
* Mapping of mod name to versions to get icons from.
|
||||
* @param {Array<Object>} items - Array of item prototypes.
|
||||
* @return {itemSheet: Jimp, itemData: Map<string, Object>} Item spritesheet and metadata.
|
||||
* @return {{itemSheet: Jimp, itemData: Map<string,Object>}}
|
||||
* 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<string, string>} modVersions -
|
||||
* @param {Map<string, string>} modVersions -
|
||||
* Mapping of mod name to version to export locale from.
|
||||
* @param modOrder {Array<string>} modOrder - Load order of the mods.
|
||||
* @param {Array<string>} modOrder - Load order of the mods.
|
||||
* @param {string} languageCode - Language to export locale for.
|
||||
* @returns {Map<string, string>} 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.
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
+18
-16
@@ -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,
|
||||
}
|
||||
};
|
||||
+112
-99
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
"use strict";
|
||||
const fileOps = require("./fileOps");
|
||||
module.exports = fileOps;
|
||||
module.exports = fileOps;
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
+9
-8
@@ -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<string>} 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,
|
||||
}
|
||||
};
|
||||
+3
-2
@@ -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,
|
||||
}
|
||||
};
|
||||
+17
-19
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -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"),
|
||||
};
|
||||
+10
-4
@@ -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,
|
||||
}
|
||||
};
|
||||
+76
-68
@@ -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,
|
||||
}
|
||||
};
|
||||
+5
-1
@@ -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,
|
||||
}
|
||||
};
|
||||
+31
-26
@@ -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,
|
||||
}
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
+29
-27
@@ -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;
|
||||
}
|
||||
};
|
||||
+32
-18
@@ -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<string, Object>} 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<string, Object} plugins -
|
||||
* @param {Map<string, Object>} 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.
|
||||
|
||||
+36
-35
@@ -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();
|
||||
|
||||
+6
-5
@@ -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" },
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+7
-4
@@ -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<number, module:lib/user.Role>} 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<number, module:lib/user.Role>} roles - role storage to modify.
|
||||
* @static
|
||||
*/
|
||||
function ensureDefaultPlayerRole(roles) {
|
||||
@@ -156,7 +157,7 @@ class User {
|
||||
|
||||
/**
|
||||
* Roles this user has
|
||||
* @type {Set<module:lib/permissions.Role}
|
||||
* @type {Set<module:lib/permissions.Role>}
|
||||
*/
|
||||
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) {
|
||||
|
||||
@@ -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 -2<sup>31</sup> and
|
||||
* 2<sup>31</sup>-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(`
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" },
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
@@ -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");
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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");
|
||||
@@ -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",
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
doleDivider,
|
||||
neuralDole,
|
||||
};
|
||||
@@ -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}
|
||||
module.exports = { Dose,Tick };
|
||||
@@ -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 = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
@@ -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 = "<td><div></div></td><td class=name></td><td class=count></td>";
|
||||
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();
|
||||
});
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -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<integer, Object>} 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 <command> [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 = {
|
||||
|
||||
+12
-11
@@ -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" }],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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: [] });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const path = require("path");
|
||||
|
||||
|
||||
@@ -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); });
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const events = require("events");
|
||||
const jwt = require("jsonwebtoken");
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
+39
-38
@@ -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 ''"));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+43
-42
@@ -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")
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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"]]));
|
||||
});
|
||||
});;
|
||||
});
|
||||
});
|
||||
+52
-51
@@ -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 <eof>",
|
||||
{
|
||||
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 <eof>",
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
"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,
|
||||
}
|
||||
};
|
||||
+32
-31
@@ -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, []),
|
||||
|
||||
+19
-18
@@ -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");
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
+6
-5
@@ -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() {
|
||||
|
||||
+11
-10
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
+14
-13
@@ -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 }]);
|
||||
});
|
||||
|
||||
+44
-43
@@ -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: {} });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+26
-29
@@ -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);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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() {
|
||||
|
||||
+6
-24
@@ -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(){
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+27
-26
@@ -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");
|
||||
});
|
||||
|
||||
+108
-107
@@ -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
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const users = require("lib/users");
|
||||
|
||||
|
||||
+5
-4
@@ -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() {
|
||||
});
|
||||
+3
-2
@@ -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");
|
||||
+8
-7
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-13
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user