Merge pull request #325 from Hornwitser/lint-2

Lint fixes 2
This commit is contained in:
Hornwitser authored and GitHub committed 2020-10-24 14:00:41 +02:00
commit a614ef8405
74 files changed
+1042 -1577

No files matched your search

+13 -4
View File
@@ -73,7 +73,7 @@ module.exports = {
"dot-location": ["error", "property"],
"dot-notation": ["warn", { "allowKeywords": true }],
"eol-last": "error",
"eqeqeq": "warn",
"eqeqeq": "error",
"func-call-spacing": "error",
"func-name-matching": "error",
"func-names": "off",
@@ -111,7 +111,7 @@ module.exports = {
"max-statements-per-line": "off",
"multiline-comment-style": ["warn", "separate-lines"],
"multiline-ternary": "off",
"new-cap": "warn",
"new-cap": "error",
"new-parens": "error",
"newline-after-var": "off",
"newline-before-return": "off",
@@ -150,7 +150,16 @@ module.exports = {
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": "off",
"no-mixed-operators": "warn",
"no-mixed-operators": [
"error",
{
"groups": [
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["in", "instanceof"],
],
},
],
"no-mixed-requires": "error",
"no-multi-assign": "error",
"no-multi-spaces": "error",
@@ -183,7 +192,7 @@ module.exports = {
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "warn",
"no-shadow": ["error", { "allow": ["yargs"] }],
"no-spaced-func": "error",
"no-sync": "error",
"no-tabs": ["error", { "allowIndentationTabs": true }],
+24
View File
@@ -20,6 +20,7 @@ Contents
- [Changelog](#changelog)
- [Supported Node.js and Factorio Version](#supported-node.js-and-factorio-version)
- [Code Style](#code-style)
- [Naming Style](#naming-style)
- [Indenting](#indenting)
- [Line Length](#line-length)
@@ -206,6 +207,29 @@ The style of the code is a bit of a mixed bag at the moment. But there
are at least a few things that have been agreed upon.
### Naming style
JavaScript variables in general uses camelCase. The exception is for
classes/constructors which use PascalCase and variables destructured
from over the wire objects.
Fields in configs and messages sent over links uses
lowercase_underscore.
JavaScript files are named using lowercase_underscore for files that
export multiple items. Files containing and exporting a single class
should be named the same as the class in PascalCase.
Imported modules from the lib package are prefixed with lib to make them
easier to distinguish, as the names tend to be very generic. E.g.:
const libLink = require("lib/link");
const libLuaTools = require("lib/lua_tools");
For lua code lowercase_underscore is used for everything.
### Strings
JavaScript and Lua code should use double quoted strings (`"string"`)
+9 -9
View File
@@ -55,7 +55,7 @@ clusterioctl. Note that it's important that the path starts with ./ or
For a plugin the most important file is the `info.js` file. Without it
the plugin will not recognized by Clusterio. Here's an example of it:
const link = require('@clusterio/lib/link'); // For messages
const libLink = require('@clusterio/lib/link'); // For messages
module.exports = {
name: "foo_frobber",
@@ -136,9 +136,9 @@ The plugin class should derive from its respective base class defined in
`lib/plugin`. For example, to define a MasterPlugin class the following
code can be used:
const plugin = require('@clusterio/lib/plugin');
const libPlugin = require('@clusterio/lib/plugin');
class MasterPlugin extends plugin.BaseMasterPlugin {
class MasterPlugin extends libPlugin.BaseMasterPlugin {
async init() {
this.foo = 42;
await this.startFrobnication();
@@ -180,9 +180,9 @@ take advantage of it by subclassing `PluginConfigGroup`, setting the
and passing it as either `MasterConfigGroup` or `InstanceConfigGroup` in
the `info.js` export. For example in info.js:
const config = require("@clusterio/lib/config");
const libConfig = require("@clusterio/lib/config");
class MasterConfigGroup extends config.PluginConfigGroup { }
class MasterConfigGroup extends libConfig.PluginConfigGroup { }
MasterConfigGroup.groupName = "foo_frobber";
MasterConfigGroup.define({
name: "level",
@@ -323,7 +323,7 @@ class. The Event constructor takes an object of properties that define
the event, for example the following could be defined in `info.js`:
messages: {
startFrobnication: new link.Event({
startFrobnication: new libLink.Event({
type: 'foo_frobber:start_frobnication',
links: ['master-slave', 'slave-instance'],
forwardTo: 'instance',
@@ -408,7 +408,7 @@ class. The Request constructor takes an object of properties that define
the event. For example, the following could be defined in `info.js`:
messages: {
reportFrobnication: new link.Request({
reportFrobnication: new libLink.Request({
type: 'foo_frobber:report_frobnication',
links: ['master-slave', 'slave-instance'],
forwardTo: 'instance',
@@ -648,9 +648,9 @@ for how to define the messages that can be sent to the master.
To have the command tree become part of clusterioctl it needs to be added
to the rootCommand tree in `addCommands` callback of the Control plugin:
const plugin = require("@clusterio/lib/plugin");
const libPlugin = require("@clusterio/lib/plugin");
class ControlPlugin extends plugin.BaseControlPlugin {
class ControlPlugin extends libPlugin.BaseControlPlugin {
async addCommands(rootCommand) {
rootCommand.add(fooFrobberCommands);
}
+115 -115
View File
@@ -13,12 +13,12 @@ const chalk = require("chalk");
const events = require("events");
const path = require("path");
const link = require("@clusterio/lib/link");
const errors = require("@clusterio/lib/errors");
const config = require("@clusterio/lib/config");
const plugin = require("@clusterio/lib/plugin");
const command = require("@clusterio/lib/command");
const sharedCommands = require("@clusterio/lib/shared_commands");
const libLink = require("@clusterio/lib/link");
const libErrors = require("@clusterio/lib/errors");
const libConfig = require("@clusterio/lib/config");
const libPlugin = require("@clusterio/lib/plugin");
const libCommand = require("@clusterio/lib/command");
const libSharedCommands = require("@clusterio/lib/shared_commands");
/**
@@ -59,16 +59,16 @@ function formatOutputColored(output) {
return time + info + output.message;
}
const slaveCommands = new command.CommandTree({ name: "slave", description: "Slave management" });
slaveCommands.add(new command.Command({
const slaveCommands = new libCommand.CommandTree({ name: "slave", description: "Slave management" });
slaveCommands.add(new libCommand.Command({
definition: [["list", "l"], "List slaves connected to the master"],
handler: async function(args, control) {
let response = await link.messages.listSlaves.send(control);
let response = await libLink.messages.listSlaves.send(control);
console.log(asTable(response.list));
},
}));
slaveCommands.add(new command.Command({
slaveCommands.add(new libCommand.Command({
definition: ["generate-token", "Generate token for a slave", (yargs) => {
yargs.option("id", { type: "number", nargs: 1, describe: "Slave id", demandOption: true });
}],
@@ -78,7 +78,7 @@ slaveCommands.add(new command.Command({
},
}));
slaveCommands.add(new command.Command({
slaveCommands.add(new libCommand.Command({
definition: ["create-config", "Create slave config", (yargs) => {
yargs.option("id", { type: "number", nargs: 1, describe: "Slave id", default: null });
yargs.option("name", { type: "string", nargs: 1, describe: "Slave name", default: null });
@@ -90,7 +90,7 @@ slaveCommands.add(new command.Command({
});
}],
handler: async function(args, control) {
let response = await link.messages.createSlaveConfig.send(control, {
let response = await libLink.messages.createSlaveConfig.send(control, {
id: args.id, name: args.name, generate_token: args.generateToken,
});
@@ -103,7 +103,7 @@ slaveCommands.add(new command.Command({
await fs.outputFile(args.output, content, { flag: "wx" });
} catch (err) {
if (err.code === "EEXIST") {
throw new errors.CommandError(`File ${args.output} already exists`);
throw new libErrors.CommandError(`File ${args.output} already exists`);
}
throw err;
}
@@ -112,18 +112,18 @@ slaveCommands.add(new command.Command({
}));
const instanceCommands = new command.CommandTree({
const instanceCommands = new libCommand.CommandTree({
name: "instance", alias: ["i"], description: "Instance management",
});
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: [["list", "l"], "List instances known to the master"],
handler: async function(args, control) {
let response = await link.messages.listInstances.send(control);
let response = await libLink.messages.listInstances.send(control);
console.log(asTable(response.list));
},
}));
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: ["create <name>", "Create an instance", (yargs) => {
// XXX TODO: set any specific options?
yargs.positional("name", { describe: "Instance name", type: "string" });
@@ -132,27 +132,27 @@ instanceCommands.add(new command.Command({
});
}],
handler: async function(args, control) {
let instanceConfig = new config.InstanceConfig();
let instanceConfig = new libConfig.InstanceConfig();
await instanceConfig.init();
if (args.id) {
instanceConfig.set("instance.id", args.id);
}
instanceConfig.set("instance.name", args.name);
let serialized_config = instanceConfig.serialize();
let response = await link.messages.createInstance.send(control, { serialized_config });
let response = await libLink.messages.createInstance.send(control, { serialized_config });
},
}));
const instanceConfigCommands = new command.CommandTree({
const instanceConfigCommands = new libCommand.CommandTree({
name: "config", alias: ["c"], description: "Instance config management",
});
instanceConfigCommands.add(new command.Command({
instanceConfigCommands.add(new libCommand.Command({
definition: ["list <instance>", "List configuration for an instance", (yargs) => {
yargs.positional("instance", { describe: "Instance to list config for", type: "string" });
}],
handler: async function(args, control) {
let instanceId = await command.resolveInstance(control, args.instance);
let response = await link.messages.getInstanceConfig.send(control, { instance_id: instanceId });
let instanceId = await libCommand.resolveInstance(control, args.instance);
let response = await libLink.messages.getInstanceConfig.send(control, { instance_id: instanceId });
for (let group of response.serialized_config.groups) {
for (let [name, value] of Object.entries(group.fields)) {
@@ -162,15 +162,15 @@ instanceConfigCommands.add(new command.Command({
},
}));
instanceConfigCommands.add(new command.Command({
instanceConfigCommands.add(new libCommand.Command({
definition: ["set <instance> <field> <value>", "Set field in instance config", (yargs) => {
yargs.positional("instance", { describe: "Instance to set config on", type: "string" });
yargs.positional("field", { describe: "Field to set", type: "string" });
yargs.positional("value", { describe: "Value to set", type: "string" });
}],
handler: async function(args, control) {
let instanceId = await command.resolveInstance(control, args.instance);
await link.messages.setInstanceConfigField.send(control, {
let instanceId = await libCommand.resolveInstance(control, args.instance);
await libLink.messages.setInstanceConfigField.send(control, {
instance_id: instanceId,
field: args.field,
value: args.value,
@@ -178,7 +178,7 @@ instanceConfigCommands.add(new command.Command({
},
}));
instanceConfigCommands.add(new command.Command({
instanceConfigCommands.add(new libCommand.Command({
definition: ["set-prop <instance> <field> <prop> <value>", "Set property of field in instance config", (yargs) => {
yargs.positional("instance", { describe: "Instance to set config on", type: "string" });
yargs.positional("field", { describe: "Field to set", type: "string" });
@@ -186,8 +186,8 @@ instanceConfigCommands.add(new command.Command({
yargs.positional("value", { describe: "JSON parsed value to set", type: "string" });
}],
handler: async function(args, control) {
let instanceId = await command.resolveInstance(control, args.instance);
await link.messages.setInstanceConfigProp.send(control, {
let instanceId = await libCommand.resolveInstance(control, args.instance);
await libLink.messages.setInstanceConfigProp.send(control, {
instance_id: instanceId,
field: args.field,
prop: args.prop,
@@ -197,48 +197,48 @@ instanceConfigCommands.add(new command.Command({
}));
instanceCommands.add(instanceConfigCommands);
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: ["assign <instance> <slave>", "Assign instance to a slave", (yargs) => {
yargs.positional("instance", { describe: "Instance to assign", type: "string" });
yargs.positional("slave", { describe: "Slave to assign to", type: "string" });
}],
handler: async function(args, control) {
let instanceId = await command.resolveInstance(control, args.instance);
let slaveId = await command.resolveSlave(control, args.slave);
await link.messages.assignInstanceCommand.send(control, {
let instanceId = await libCommand.resolveInstance(control, args.instance);
let slaveId = await libCommand.resolveSlave(control, args.slave);
await libLink.messages.assignInstanceCommand.send(control, {
instance_id: instanceId,
slave_id: slaveId,
});
},
}));
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: ["create-save <instance>", "Create a new save on an instance", (yargs) => {
yargs.positional("instance", { describe: "Instance to create on", type: "string" });
}],
handler: async function(args, control) {
let instanceId = await command.resolveInstance(control, args.instance);
await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await link.messages.createSave.send(control, {
let instanceId = await libCommand.resolveInstance(control, args.instance);
await libLink.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await libLink.messages.createSave.send(control, {
instance_id: instanceId,
});
},
}));
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: ["export-data <instance>", "Export item icons and locale from instance", (yargs) => {
yargs.positional("instance", { describe: "Instance to export from", type: "string" });
}],
handler: async function(args, control) {
let instanceId = await command.resolveInstance(control, args.instance);
await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await link.messages.exportData.send(control, {
let instanceId = await libCommand.resolveInstance(control, args.instance);
await libLink.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await libLink.messages.exportData.send(control, {
instance_id: instanceId,
});
},
}));
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: ["start <instance>", "Start instance", (yargs) => {
yargs.positional("instance", { describe: "Instance to start", type: "string" });
yargs.options({
@@ -247,16 +247,16 @@ instanceCommands.add(new command.Command({
});
}],
handler: async function(args, control) {
let instanceId = await command.resolveInstance(control, args.instance);
await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await link.messages.startInstance.send(control, {
let instanceId = await libCommand.resolveInstance(control, args.instance);
await libLink.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await libLink.messages.startInstance.send(control, {
instance_id: instanceId,
save: args.save || null,
});
},
}));
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: ["load-scenario <instance> <scenario>", "Start instance by loading a scenario", (yargs) => {
yargs.positional("instance", { describe: "Instance to start", type: "string" });
yargs.positional("scenario", { describe: "Scenario to load", type: "string" });
@@ -265,47 +265,47 @@ instanceCommands.add(new command.Command({
});
}],
handler: async function(args, control) {
let instanceId = await command.resolveInstance(control, args.instance);
await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await link.messages.loadScenario.send(control, {
let instanceId = await libCommand.resolveInstance(control, args.instance);
await libLink.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await libLink.messages.loadScenario.send(control, {
instance_id: instanceId,
scenario: args.scenario || null,
});
},
}));
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: ["stop <instance>", "Stop instance", (yargs) => {
yargs.positional("instance", { describe: "Instance to stop", type: "string" });
}],
handler: async function(args, control) {
let instanceId = await command.resolveInstance(control, args.instance);
await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await link.messages.stopInstance.send(control, {
let instanceId = await libCommand.resolveInstance(control, args.instance);
await libLink.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await libLink.messages.stopInstance.send(control, {
instance_id: instanceId,
});
},
}));
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: ["delete <instance>", "Delete instance", (yargs) => {
yargs.positional("instance", { describe: "Instance to delete", type: "string" });
}],
handler: async function(args, control) {
let response = await link.messages.deleteInstance.send(control, {
instance_id: await command.resolveInstance(control, args.instance),
let response = await libLink.messages.deleteInstance.send(control, {
instance_id: await libCommand.resolveInstance(control, args.instance),
});
},
}));
instanceCommands.add(new command.Command({
instanceCommands.add(new libCommand.Command({
definition: ["send-rcon <instance> <command>", "Send RCON command", (yargs) => {
yargs.positional("instance", { describe: "Instance to send to", type: "string" });
yargs.positional("command", { describe: "command to send", type: "string" });
}],
handler: async function(args, control) {
let response = await link.messages.sendRcon.send(control, {
instance_id: await command.resolveInstance(control, args.instance),
let response = await libLink.messages.sendRcon.send(control, {
instance_id: await libCommand.resolveInstance(control, args.instance),
command: args.command,
});
@@ -314,26 +314,26 @@ instanceCommands.add(new command.Command({
},
}));
const permissionCommands = new command.CommandTree({ name: "permission", description: "Permission inspection" });
permissionCommands.add(new command.Command({
const permissionCommands = new libCommand.CommandTree({ name: "permission", description: "Permission inspection" });
permissionCommands.add(new libCommand.Command({
definition: [["list", "l"], "List permissions in the cluster"],
handler: async function(args, control) {
let response = await link.messages.listPermissions.send(control);
let response = await libLink.messages.listPermissions.send(control);
console.log(asTable(response.list));
},
}));
const roleCommands = new command.CommandTree({ name: "role", description: "Role management" });
roleCommands.add(new command.Command({
const roleCommands = new libCommand.CommandTree({ name: "role", description: "Role management" });
roleCommands.add(new libCommand.Command({
definition: [["list", "l"], "List roles in the cluster"],
handler: async function(args, control) {
let response = await link.messages.listRoles.send(control);
let response = await libLink.messages.listRoles.send(control);
console.log(asTable(response.list));
},
}));
roleCommands.add(new command.Command({
roleCommands.add(new libCommand.Command({
definition: ["create <name>", "Create a new role", (yargs) => {
yargs.positional("name", { describe: "Name of role to create", type: "string" });
yargs.options({
@@ -342,7 +342,7 @@ roleCommands.add(new command.Command({
});
}],
handler: async function(args, control) {
let response = await link.messages.createRole.send(control, {
let response = await libLink.messages.createRole.send(control, {
name: args.name,
description: args.description,
permissions: args.permissions,
@@ -351,7 +351,7 @@ roleCommands.add(new command.Command({
},
}));
roleCommands.add(new command.Command({
roleCommands.add(new libCommand.Command({
definition: ["edit <role>", "Edit existing role", (yargs) => {
yargs.positional("role", { describe: "Role to edit", type: "string" });
yargs.options({
@@ -362,7 +362,7 @@ roleCommands.add(new command.Command({
});
}],
handler: async function(args, control) {
let role = await command.retrieveRole(control, args.role);
let role = await libCommand.retrieveRole(control, args.role);
if (args.name !== undefined) {
role.name = args.name;
@@ -373,44 +373,44 @@ roleCommands.add(new command.Command({
if (args.permissions !== undefined) {
role.permissions = args.permissions;
}
await link.messages.updateRole.send(control, role);
await libLink.messages.updateRole.send(control, role);
if (args.grantDefault) {
await link.messages.grantDefaultRolePermissions.send(control, { id: role.id });
await libLink.messages.grantDefaultRolePermissions.send(control, { id: role.id });
}
},
}));
roleCommands.add(new command.Command({
roleCommands.add(new libCommand.Command({
definition: ["delete <role>", "Delete role", (yargs) => {
yargs.positional("role", { describe: "Role to delete", type: "string" });
}],
handler: async function(args, control) {
let role = await command.retrieveRole(control, args.role);
await link.messages.deleteRole.send(control, { id: role.id });
let role = await libCommand.retrieveRole(control, args.role);
await libLink.messages.deleteRole.send(control, { id: role.id });
},
}));
const userCommands = new command.CommandTree({ name: "user", alias: ["u"], description: "User management" });
userCommands.add(new command.Command({
const userCommands = new libCommand.CommandTree({ name: "user", alias: ["u"], description: "User management" });
userCommands.add(new libCommand.Command({
definition: [["list", "l"], "List user in the cluster"],
handler: async function(args, control) {
let response = await link.messages.listUsers.send(control);
let response = await libLink.messages.listUsers.send(control);
console.log(asTable(response.list));
},
}));
userCommands.add(new command.Command({
userCommands.add(new libCommand.Command({
definition: ["create <name>", "Create a user", (yargs) => {
yargs.positional("name", { describe: "Name of user to create", type: "string" });
}],
handler: async function(args, control) {
await link.messages.createUser.send(control, { name: args.name });
await libLink.messages.createUser.send(control, { name: args.name });
},
}));
userCommands.add(new command.Command({
userCommands.add(new libCommand.Command({
definition: ["set-admin <user>", "Promote or demote a user to admin", (yargs) => {
yargs.positional("user", { describe: "Name of user set admin status for", type: "string" });
yargs.options({
@@ -419,13 +419,13 @@ userCommands.add(new command.Command({
});
}],
handler: async function(args, control) {
await link.messages.setUserAdmin.send(control, {
await libLink.messages.setUserAdmin.send(control, {
name: args.user, create: args.create, admin: !args.revoke,
});
},
}));
userCommands.add(new command.Command({
userCommands.add(new libCommand.Command({
definition: ["set-whitelisted <user>", "Add or remove user from the whitelist", (yargs) => {
yargs.positional("user", { describe: "Name of user to set whitelist status for", type: "string" });
yargs.options({
@@ -434,13 +434,13 @@ userCommands.add(new command.Command({
});
}],
handler: async function(args, control) {
await link.messages.setUserWhitelisted.send(control, {
await libLink.messages.setUserWhitelisted.send(control, {
name: args.user, create: args.create, whitelisted: !args.remove,
});
},
}));
userCommands.add(new command.Command({
userCommands.add(new libCommand.Command({
definition: ["set-banned <user>", "Ban or pardon user from banlist", (yargs) => {
yargs.positional("user", { describe: "Name of user to set ban status for", type: "string" });
yargs.options({
@@ -450,19 +450,19 @@ userCommands.add(new command.Command({
});
}],
handler: async function(args, control) {
await link.messages.setUserBanned.send(control, {
await libLink.messages.setUserBanned.send(control, {
name: args.user, create: args.create, banned: !args.pardon, reason: args.reason,
});
},
}));
userCommands.add(new command.Command({
userCommands.add(new libCommand.Command({
definition: ["set-roles <user> [roles...]", "Replace user roles", (yargs) => {
yargs.positional("user", { describe: "Name of user to change roles for", type: "string" });
yargs.positional("roles", { describe: "roles to assign", type: "string" });
}],
handler: async function(args, control) {
let response = await link.messages.listRoles.send(control);
let response = await libLink.messages.listRoles.send(control);
let resolvedRoles = [];
for (let roleName of args.roles) {
@@ -481,29 +481,29 @@ userCommands.add(new command.Command({
}
if (!found) {
throw new errors.CommandError(`No role named ${roleName}`);
throw new libErrors.CommandError(`No role named ${roleName}`);
}
}
}
await link.messages.updateUserRoles.send(control, { name: args.user, roles: resolvedRoles });
await libLink.messages.updateUserRoles.send(control, { name: args.user, roles: resolvedRoles });
},
}));
userCommands.add(new command.Command({
userCommands.add(new libCommand.Command({
definition: ["delete <user>", "Delete user", (yargs) => {
yargs.positional("user", { describe: "Name of user to delete", type: "string" });
}],
handler: async function(args, control) {
await link.messages.deleteUser.send(control, { name: args.user });
await libLink.messages.deleteUser.send(control, { name: args.user });
},
}));
const debugCommands = new command.CommandTree({ name: "debug", description: "Debugging utilities" });
debugCommands.add(new command.Command({
const debugCommands = new libCommand.CommandTree({ name: "debug", description: "Debugging utilities" });
debugCommands.add(new libCommand.Command({
definition: ["dump-ws", "Dump WebSocket messages sent and received by master", (yargs) => { }],
handler: async function(args, control) {
await link.messages.debugDumpWs.send(control);
await libLink.messages.debugDumpWs.send(control);
return new Promise(() => {});
},
}));
@@ -513,7 +513,7 @@ debugCommands.add(new command.Command({
* Connector for control connection to master server
* @private
*/
class ControlConnector extends link.WebSocketClientConnector {
class ControlConnector extends libLink.WebSocketClientConnector {
constructor(url, reconnectDelay, token) {
super(url, reconnectDelay);
this._token = token;
@@ -535,11 +535,11 @@ class ControlConnector extends link.WebSocketClientConnector {
* Connects to the master server over WebSocket and sends commands to it.
* @static
*/
class Control extends link.Link {
class Control extends libLink.Link {
constructor(connector, controlPlugins) {
super("control", "master", connector);
link.attachAllMessages(this);
libLink.attachAllMessages(this);
/**
* Mapping of plugin names to their instance for loaded plugins.
@@ -547,7 +547,7 @@ class Control extends link.Link {
*/
this.plugins = controlPlugins;
for (let controlPlugin of controlPlugins.values()) {
plugin.attachPluginMessages(this, controlPlugin.info, controlPlugin);
libPlugin.attachPluginMessages(this, controlPlugin.info, controlPlugin);
}
}
@@ -564,9 +564,9 @@ class Control extends link.Link {
this.connector.setTimeout(30);
try {
await link.messages.prepareDisconnect.send(this);
await libLink.messages.prepareDisconnect.send(this);
} catch (err) {
if (!(err instanceof errors.SessionLost)) {
if (!(err instanceof libErrors.SessionLost)) {
throw err;
}
}
@@ -593,8 +593,8 @@ async function startControl() {
default: "plugin-list.json",
type: "string",
})
.command("plugin", "Manage available plugins", sharedCommands.pluginCommand)
.command("control-config", "Manage Control config", config.configCommand)
.command("plugin", "Manage available plugins", libSharedCommands.pluginCommand)
.command("control-config", "Manage Control config", libConfig.configCommand)
.wrap(yargs.terminalWidth())
.help(false) // Disable help to avoid triggering it on the first parse.
;
@@ -614,12 +614,12 @@ async function startControl() {
// If the command is plugin management we don't try to load plugins
if (args._[0] === "plugin") {
await sharedCommands.handlePluginCommand(args, pluginList, args.pluginList);
await libSharedCommands.handlePluginCommand(args, pluginList, args.pluginList);
return;
}
// Add all cluster management commands including ones from plugins
const rootCommands = new command.CommandTree({ name: "clusterioctl", description: "Manage cluster" });
const rootCommands = new libCommand.CommandTree({ name: "clusterioctl", description: "Manage cluster" });
rootCommands.add(slaveCommands);
rootCommands.add(instanceCommands);
rootCommands.add(permissionCommands);
@@ -628,9 +628,9 @@ async function startControl() {
rootCommands.add(debugCommands);
console.log("Loading Plugin info");
let pluginInfos = await plugin.loadPluginInfos(pluginList);
config.registerPluginConfigGroups(pluginInfos);
config.finalizeConfigs();
let pluginInfos = await libPlugin.loadPluginInfos(pluginList);
libConfig.registerPluginConfigGroups(pluginInfos);
libConfig.finalizeConfigs();
let controlPlugins = new Map();
for (let pluginInfo of pluginInfos) {
@@ -659,7 +659,7 @@ async function startControl() {
;
console.log(`Loading config from ${args.config}`);
let controlConfig = new config.ControlConfig();
let controlConfig = new libConfig.ControlConfig();
try {
await controlConfig.load(JSON.parse(await fs.readFile(args.config)));
@@ -680,14 +680,14 @@ async function startControl() {
// Handle the control-config command before trying to connect.
if (args._[0] === "control-config") {
await config.handleConfigCommand(args, controlConfig, args.config);
await libConfig.handleConfigCommand(args, controlConfig, args.config);
return;
}
// Determine which command is being executed.
let commandPath = [...args._];
let targetCommand = rootCommands;
while (commandPath.length && targetCommand instanceof command.CommandTree) {
while (commandPath.length && targetCommand instanceof libCommand.CommandTree) {
targetCommand = targetCommand.get(commandPath.shift());
}
@@ -707,8 +707,8 @@ async function startControl() {
try {
await controlConnector.connect();
} catch(err) {
if (err instanceof errors.AuthenticationFailed) {
throw new errors.StartupError(err.message);
if (err instanceof libErrors.AuthenticationFailed) {
throw new libErrors.StartupError(err.message);
}
throw err;
}
@@ -727,11 +727,11 @@ async function startControl() {
} catch (err) {
keepOpen = false;
if (err instanceof errors.CommandError) {
if (err instanceof libErrors.CommandError) {
console.error(`Error running command: ${err.message}`);
process.exitCode = 1;
} else if (err instanceof errors.RequestError) {
} else if (err instanceof libErrors.RequestError) {
console.error(`Error sending request: ${err.message}`);
process.exitCode = 1;
@@ -763,7 +763,7 @@ I version of clusterio. Expect things to break. I
`
);
startControl().catch(err => {
if (!(err instanceof errors.StartupError)) {
if (!(err instanceof libErrors.StartupError)) {
console.error(`
+----------------------------------------------------------------+
| Unexpected error occured while starting control, please report |
+8 -8
View File
@@ -7,8 +7,8 @@
* @module lib/command
*/
"use strict";
const link = require("@clusterio/lib/link");
const errors = require("@clusterio/lib/errors");
const libLink = require("@clusterio/lib/link");
const libErrors = require("@clusterio/lib/errors");
/**
* Represents a command that can be runned by clusterioctl
@@ -150,7 +150,7 @@ async function resolveSlave(client, slaveName) {
if (/^-?\d+$/.test(slaveName)) {
slaveId = parseInt(slaveName, 10);
} else {
let response = await link.messages.listSlaves.send(client);
let response = await libLink.messages.listSlaves.send(client);
for (let slave of response.list) {
if (slave.name === slaveName) {
slaveId = slave.id;
@@ -159,7 +159,7 @@ async function resolveSlave(client, slaveName) {
}
if (slaveId === undefined) {
throw new errors.CommandError(`No slave named ${slaveName}`);
throw new libErrors.CommandError(`No slave named ${slaveName}`);
}
}
@@ -182,7 +182,7 @@ async function resolveInstance(client, instanceName) {
if (/^-?\d+$/.test(instanceName)) {
instanceId = parseInt(instanceName, 10);
} else {
let response = await link.messages.listInstances.send(client);
let response = await libLink.messages.listInstances.send(client);
for (let instance of response.list) {
if (instance.name === instanceName) {
instanceId = instance.id;
@@ -191,7 +191,7 @@ async function resolveInstance(client, instanceName) {
}
if (instanceId === undefined) {
throw new errors.CommandError(`No instance named ${instanceName}`);
throw new libErrors.CommandError(`No instance named ${instanceName}`);
}
}
@@ -210,7 +210,7 @@ async function resolveInstance(client, instanceName) {
* @static
*/
async function retrieveRole(client, roleName) {
let response = await link.messages.listRoles.send(client);
let response = await libLink.messages.listRoles.send(client);
let resolvedRole;
if (/^-?\d+$/.test(roleName)) {
@@ -232,7 +232,7 @@ async function retrieveRole(client, roleName) {
}
if (!resolvedRole) {
throw new errors.CommandError(`No role named ${roleName}`);
throw new libErrors.CommandError(`No role named ${roleName}`);
}
return resolvedRole;
+1 -6
View File
@@ -443,12 +443,7 @@ class ConfigGroup {
}
let prev = this._fields.get(name);
let updated = {};
if (prev !== null) {
for (let [key, value] of Object.entries(prev)) {
updated[key] = value;
}
}
let updated = {...prev || {}};
updated[prop] = value;
this._fields.set(name, updated);
+2 -2
View File
@@ -55,7 +55,7 @@ async function loadJsonAsMap(filePath) {
return new Map(Object.entries(parsed));
} catch (err) {
if (err.code == "ENOENT") {
if (err.code === "ENOENT") {
// File does not exist, return empty map
return new Map();
@@ -117,7 +117,7 @@ async function loadJsonArrayAsMap(filePath) {
return map;
} catch (err) {
if (err.code == "ENOENT") {
if (err.code === "ENOENT") {
// File does not exist, return empty map
return new Map();
+8 -8
View File
@@ -6,7 +6,7 @@ const ini = require("ini");
const Jimp = require("jimp");
const JSZip = require("jszip");
const buildMod = require("@clusterio/lib/build_mod");
const libBuildMod = require("@clusterio/lib/build_mod");
/**
* Generate the export mod needed for exportData
@@ -29,7 +29,7 @@ async function generateExportMod(server) {
}
}
await buildMod.build({
await libBuildMod.build({
clean: false,
build: true,
pack: true,
@@ -130,19 +130,19 @@ async function exportItems(server, modVersions, items) {
let pos = 0;
let iconCache = new Map();
async function loadIcon(path, iconSize, iconMipmaps) {
let icon = iconCache.get(path);
async function loadIcon(iconPath, iconSize, iconMipmaps) {
let icon = iconCache.get(iconPath);
if (icon === undefined) {
let fileContent = await loadFile(server, modVersions, path);
let fileContent = await loadFile(server, modVersions, iconPath);
if (fileContent) {
icon = await Jimp.read(fileContent);
icon.crop(0, 0, iconSize, iconSize);
iconCache.set(path, icon);
iconCache.set(iconPath, icon);
} else {
icon = null;
console.log(`Warning: ${path} not found`);
console.log(`Warning: ${iconPath} not found`);
}
iconCache.set(path, icon);
iconCache.set(iconPath, icon);
}
return icon;
}
+7 -7
View File
@@ -3,11 +3,11 @@
"use strict";
const events = require("events");
const fs = require("fs-extra");
const jszip = require("jszip");
const JSZip = require("jszip");
const path = require("path");
const semver = require("semver");
const hash = require("@clusterio/lib/hash");
const libHash = require("@clusterio/lib/hash");
const knownScenarios = {
@@ -27,7 +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.
* @param {JSZip} zip - Zip to search through.
* @returns {string} name of the root folder.
* @memberof module:lib/factorio
* @private
@@ -169,8 +169,8 @@ function reorderDependencies(modules) {
// the module depends on a module that satisfy any of these conditions.
let remaining = new Map();
for (let modules of hold.values()) {
for (let module of modules) {
for (let heldModules of hold.values()) {
for (let module of heldModules) {
remaining.set(module.name, module);
}
}
@@ -213,7 +213,7 @@ function reorderDependencies(modules) {
* @memberof module:lib/factorio
*/
async function patch(savePath, modules) {
let zip = await jszip.loadAsync(await fs.readFile(savePath));
let zip = await JSZip.loadAsync(await fs.readFile(savePath));
let root = zip.folder(findRoot(zip));
let patchInfoFile = root.file("clusterio.json");
@@ -225,7 +225,7 @@ async function patch(savePath, modules) {
// No info file present, try to detect if it's a known compatible scenario.
} else {
let controlStream = root.file("control.lua").nodeStream("nodebuffer");
let controlHash = await hash.hashStream(controlStream);
let controlHash = await libHash.hashStream(controlStream);
if (controlHash in knownScenarios) {
patchInfo = {
+3 -3
View File
@@ -9,7 +9,7 @@ const crypto = require("crypto");
const ini = require("ini");
const rconClient = require("rcon-client");
const errors = require("@clusterio/lib/errors");
const libErrors = require("@clusterio/lib/errors");
/**
@@ -183,7 +183,7 @@ async function generatePassword(length) {
if (validChar(byte)) {
password += String.fromCharCode(byte);
if (password.length == length) {
if (password.length === length) {
return password;
}
}
@@ -705,7 +705,7 @@ class FactorioServer extends events.EventEmitter {
if (this._state !== "stopping") {
if (this._unexpected.length === 0) {
if (signal === "SIGKILL") {
this.emit("error", new errors.EnvironmentError(
this.emit("error", new libErrors.EnvironmentError(
"Factorio server was unexpectedly killed, is the system low on memory?"
));
-3
View File
@@ -1,3 +0,0 @@
"use strict";
const fileOps = require("./fileOps");
module.exports = fileOps;
@@ -1,5 +1,5 @@
/**
* @module lib/fileOps
* @module lib/file_ops
*/
"use strict";
const fs = require("fs-extra");
+12 -12
View File
@@ -3,8 +3,8 @@
const events = require("events");
const WebSocket = require("isomorphic-ws");
const schema = require("@clusterio/lib/schema");
const errors = require("@clusterio/lib/errors");
const libSchema = require("@clusterio/lib/schema");
const libErrors = require("@clusterio/lib/errors");
/**
@@ -60,10 +60,10 @@ class WebSocketBaseConnector extends events.EventEmitter {
}
_processHeartbeat(message) {
if (!schema.heartbeat(message)) {
if (!libSchema.heartbeat(message)) {
console.log("SOCKET | closing after received invalid heartbeat:", message);
this._socket.close(1002, "Invalid heartbeat");
console.log(schema.heartbeat.errors);
console.log(libSchema.heartbeat.errors);
return;
}
@@ -100,7 +100,7 @@ class WebSocketBaseConnector extends events.EventEmitter {
*/
send(type, data) {
if (!["handshake", "connected", "closing"].includes(this._state)) {
throw new errors.SessionLost("No session");
throw new libErrors.SessionLost("No session");
}
let message = { seq: this._seq, type, data };
this._sendBuffer.push(message);
@@ -287,7 +287,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
console.log(`SOCKET | Close (code: ${event.code}, reason: ${event.reason})`);
// Authentication failed
if (event.code === 4003) {
this.emit("error", new errors.AuthenticationFailed(event.reason));
this.emit("error", new libErrors.AuthenticationFailed(event.reason));
this._state = "closing";
}
@@ -345,14 +345,14 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
}
_processHandshake(message) {
if (!schema.serverHandshake(message)) {
if (!libSchema.serverHandshake(message)) {
console.log("SOCKET | closing after received invalid handshake:", message);
this._socket.close(1002, "Invalid handshake");
return;
}
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) {
@@ -371,8 +371,8 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
this._sessionToken = data.session_token;
this._heartbeatInterval = data.heartbeat_interval;
this.startHeartbeat();
for (let message of this._sendBuffer) {
this._socket.send(JSON.stringify(message));
for (let bufferedMessage of this._sendBuffer) {
this._socket.send(JSON.stringify(bufferedMessage));
}
this._startedReconnect = null;
this._connected = true;
@@ -384,8 +384,8 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
this._heartbeatInterval = data.heartbeat_interval;
this.startHeartbeat();
this._dropSendBufferSeq(data.last_seq);
for (let message of this._sendBuffer) {
this._socket.send(JSON.stringify(message));
for (let bufferedMessage of this._sendBuffer) {
this._socket.send(JSON.stringify(bufferedMessage));
}
this._startedReconnect = null;
this._connected = true;
+10 -10
View File
@@ -1,8 +1,8 @@
// Implementation of Link class
"use strict";
const schema = require("@clusterio/lib/schema");
const errors = require("@clusterio/lib/errors");
const libSchema = require("@clusterio/lib/schema");
const libErrors = require("@clusterio/lib/errors");
const messages = require("./messages");
// Some definitions for the terminology used here:
@@ -34,7 +34,7 @@ class Link {
try {
this.processMessage(payload);
} catch (err) {
if (err instanceof errors.InvalidMessage) {
if (err instanceof libErrors.InvalidMessage) {
console.error(`Invalid message on ${this.source}-${this.target} link: ${err.message}`);
if (err.errors) {
console.error(err.errors);
@@ -48,7 +48,7 @@ class Link {
connector.on("invalidate", () => {
for (let waiterType of this._waiters.values()) {
for (let waiter of waiterType) {
waiter.reject(new errors.SessionLost("Session Lost"));
waiter.reject(new libErrors.SessionLost("Session Lost"));
}
}
@@ -58,7 +58,7 @@ class Link {
connector.on("close", () => {
for (let waiterType of this._waiters.values()) {
for (let waiter of waiterType) {
waiter.reject(new errors.SessionLost("Session Closed"));
waiter.reject(new libErrors.SessionLost("Session Closed"));
}
}
@@ -78,17 +78,17 @@ class Link {
* handlers/waiters were invoked to handle the message.
*/
processMessage(message) {
if (!schema.message(message)) {
throw new errors.InvalidMessage("Malformed", schema.message.errors);
if (!libSchema.message(message)) {
throw new libErrors.InvalidMessage("Malformed", libSchema.message.errors);
}
let validator = this._validators.get(message.type);
if (!validator) {
throw new errors.InvalidMessage(`No validator for ${message.type} on ${this.source}-${this.target}`);
throw new libErrors.InvalidMessage(`No validator for ${message.type} on ${this.source}-${this.target}`);
}
if (!validator(message)) {
throw new errors.InvalidMessage(`Validation failed for ${message.type}`, validator.errors);
throw new libErrors.InvalidMessage(`Validation failed for ${message.type}`, validator.errors);
}
let hadHandlers = this._processHandler(message);
@@ -96,7 +96,7 @@ class Link {
if (!hadWaiters && !hadHandlers) {
// XXX console.error(`Unhandled message: ${JSON.stringify(message, null, 4)}`);
throw new errors.InvalidMessage(`Unhandled message ${message.type}`);
throw new libErrors.InvalidMessage(`Unhandled message ${message.type}`);
}
}
+7 -7
View File
@@ -1,7 +1,7 @@
// Message definitions for links
"use strict";
const schema = require("@clusterio/lib/schema");
const errors = require("@clusterio/lib/errors");
const libSchema = require("@clusterio/lib/schema");
const libErrors = require("@clusterio/lib/errors");
/**
* Represents a message that can be sent over the link
@@ -88,7 +88,7 @@ class Request extends Message {
throw new Error(`Invalid forwardTo value ${forwardTo}`);
}
this._requestValidator = schema.compile({
this._requestValidator = libSchema.compile({
$schema: "http://json-schema.org/draft-07/schema#",
properties: {
"type": { const: this.requestType },
@@ -100,7 +100,7 @@ class Request extends Message {
},
});
this._responseValidator = schema.compile({
this._responseValidator = libSchema.compile({
$schema: "http://json-schema.org/draft-07/schema#",
properties: {
"type": { const: this.responseType },
@@ -195,7 +195,7 @@ class Request extends Message {
link.connector.send(this.responseType, { ...response, seq: message.seq });
}).catch(err => {
if (!(err instanceof errors.RequestError)) {
if (!(err instanceof libErrors.RequestError)) {
console.error(`Unexpected error while responding to ${this.requestType}`, err);
}
link.connector.send(this.responseType, { seq: message.seq, error: err.message });
@@ -223,7 +223,7 @@ class Request extends Message {
let seq = link.connector.send(this.requestType, data);
let responseMessage = await link.waitFor(this.responseType, { seq });
if (responseMessage.data.error) {
throw new errors.RequestError(responseMessage.data.error);
throw new libErrors.RequestError(responseMessage.data.error);
}
return responseMessage.data;
}
@@ -702,7 +702,7 @@ class Event extends Message {
}
this.eventType = type + "_event";
this._eventValidator = schema.compile({
this._eventValidator = libSchema.compile({
$schema: "http://json-schema.org/draft-07/schema#",
properties: {
"type": { const: this.eventType },
@@ -1,6 +1,6 @@
/**
* Utilities for dealing with Lua
* @module lib/luaTools
* @module lib/lua_tools
*/
"use strict";
-46
View File
@@ -1,46 +0,0 @@
/**
* Helper functions related to objects
* @module lib/objectOps
*/
"use strict";
/**
* 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} 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;
try {
x = JSON.parse(string);
} catch (e){
return false;
}
if(typeof x == "object" || typeof string == "object"){
stringIsJson = true;
}
return stringIsJson;
};
+3 -3
View File
@@ -5,7 +5,7 @@
"use strict";
const path = require("path");
const errors = require("@clusterio/lib/errors");
const libErrors = require("@clusterio/lib/errors");
/**
@@ -409,11 +409,11 @@ async function loadPluginInfos(pluginList) {
pluginPackage = require(path.posix.join(pluginPath, "package.json"));
} catch (err) {
throw new errors.PluginError(pluginName, err);
throw new libErrors.PluginError(pluginName, err);
}
if (pluginInfo.name !== pluginName) {
throw new errors.EnvironmentError(
throw new libErrors.EnvironmentError(
`Expected plugin at ${pluginPath} to be named ${pluginName} but got ${pluginInfo.name}`
);
}
+56 -41
View File
@@ -13,9 +13,9 @@ class Metric {
throw new Error(`Invalid name '${name}'`);
}
for (let name of labels) {
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
throw new Error(`Invalid label '${name}'`);
for (let label of labels) {
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(label)) {
throw new Error(`Invalid label '${label}'`);
}
}
@@ -93,29 +93,48 @@ function labelsToKey(labels, metricLabels) {
function keyToLabels(key) {
let labels = new Map();
if (key !== "") for (let pair of key.split(",")) {
let [name, value] = pair.split("=", 2);
labels.set(name, value
.slice(1, -1)
.replace(/\\"/g, "\"")
.replace(/\\n/g, "\n")
.replace(/\\\\/g, "\\")
);
if (key !== "") {
for (let pair of key.split(",")) {
let [name, value] = pair.split("=", 2);
labels.set(name, value
.slice(1, -1)
.replace(/\\"/g, "\"")
.replace(/\\n/g, "\n")
.replace(/\\\\/g, "\\")
);
}
}
return labels;
}
function removeMatchingLabels(mapping, labels) {
for (let key of mapping.keys()) {
let candidate = keyToLabels(key);
let hasLabels = Object.entries(labels).every(([name, value]) => (
candidate.get(name) === value
));
if (hasLabels) {
mapping.delete(key);
}
}
}
class ValueCollector extends Collector {
constructor(type, name, help, options, childClass) {
let labels = [];
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; }
else { throw new Error(`Unrecognized option '${name}'`); }
for (let [key, value] of Object.entries(options)) {
if (key === "labels") {
labels = value;
} else if (key === "register") {
register = value;
} else if (key === "callback") {
callback = value;
} else {
throw new Error(`Unrecognized option '${key}'`);
}
}
// Make sure we don't register to the default registry if metric throws.
@@ -164,18 +183,6 @@ class ValueCollector extends Collector {
throw new Error("labels cannot be empty");
}
function removeMatchingLabels(mapping, labels) {
for (let key of mapping.keys()) {
let candidate = keyToLabels(key);
let hasLabels = Object.entries(labels).every(([name, value]) => (
candidate.get(name) === value
));
if (hasLabels) {
mapping.delete(key);
}
}
}
removeMatchingLabels(this._values, labels);
removeMatchingLabels(this._children, labels);
}
@@ -311,11 +318,11 @@ function escapeHelp(help) {
function formatValue(value) {
if (value === Infinity) {
return "+Inf";
} else if (value === -Infinity) {
return "-Inf";
} else {
return value.toString();
}
if (value === -Infinity) {
return "-Inf";
}
return value.toString();
}
async function* expositionLines(resultsIterator) {
@@ -341,11 +348,11 @@ async function* expositionLines(resultsIterator) {
}
async function exposition(resultsIterator = defaultRegistry.collect()) {
let exposition = "";
let lines = "";
for await (let line of expositionLines(resultsIterator)) {
exposition += line;
lines += line;
}
return exposition;
return lines;
}
// HTTP Content-Type for the exposition format that's implemented
@@ -356,10 +363,15 @@ function serializeResult(result, options = {}) {
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; }
else { throw new Error(`Unrecognized option '${name}'`); }
if (name === "addLabels") {
addLabels = value;
} else if (name === "metricName") {
metricName = value;
} else if (name === "metricHelp") {
metricHelp = value;
} else {
throw new Error(`Unrecognized option '${name}'`);
}
}
let samples;
@@ -371,8 +383,11 @@ 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); }
else { samples.set(`${labels},${key}`, value); }
if (labels === "") {
samples.set(key, value);
} else {
samples.set(`${labels},${key}`, value);
}
}
}
+5 -5
View File
@@ -64,11 +64,11 @@ function definePermission({ name, title, description, grantByDefault=false }) {
* @static
*/
class Role {
constructor({ id, name, description, permissions }) {
this.id = id;
this.name = name;
this.description = description;
this.permissions = new Set(permissions);
constructor(serializedRole) {
this.id = serializedRole.id;
this.name = serializedRole.name;
this.description = serializedRole.description;
this.permissions = new Set(serializedRole.permissions);
}
serialize() {
+109 -109
View File
@@ -38,21 +38,21 @@ let masterPlugins = new Map();
let pluginInfos = new Map();
let stopAcceptingNewSessions = false;
let debugEvents = new events.EventEmitter();
let pluginList = {};
let loadedPlugins = {};
let devMiddleware;
// homebrew modules
const generateSSLcert = require("./src/generate_ssl_cert");
const generateSslCert = require("./src/generate_ssl_cert");
const routes = require("./src/routes");
const database = require("@clusterio/lib/database");
const schema = require("@clusterio/lib/schema");
const link = require("@clusterio/lib/link");
const errors = require("@clusterio/lib/errors");
const plugin = require("@clusterio/lib/plugin");
const prometheus = require("@clusterio/lib/prometheus");
const config = require("@clusterio/lib/config");
const users = require("@clusterio/lib/users");
const sharedCommands = require("@clusterio/lib/shared_commands");
const libDatabase = require("@clusterio/lib/database");
const libSchema = require("@clusterio/lib/schema");
const libLink = require("@clusterio/lib/link");
const libErrors = require("@clusterio/lib/errors");
const libPlugin = require("@clusterio/lib/plugin");
const libPrometheus = require("@clusterio/lib/prometheus");
const libConfig = require("@clusterio/lib/config");
const libUsers = require("@clusterio/lib/users");
const libSharedCommands = require("@clusterio/lib/shared_commands");
const express = require("express");
const compression = require("compression");
@@ -100,34 +100,34 @@ function serveWeb(route) {
app.use(express.static(path.join(__dirname, "static")));
app.use(express.static("static")); // Used for data export files
const endpointHitCounter = new prometheus.Counter(
const endpointHitCounter = new libPrometheus.Counter(
"clusterio_master_http_endpoint_hits_total",
"How many requests a particular HTTP endpoint has gotten",
{ labels: ["route"] }
);
const wsMessageCounter = new prometheus.Counter(
const wsMessageCounter = new libPrometheus.Counter(
"clusterio_master_websocket_message_total",
"How many messages have been received over WebSocket on the master server",
{ labels: ["direction"] }
);
const wsConnectionsCounter = new prometheus.Counter(
const wsConnectionsCounter = new libPrometheus.Counter(
"clusterio_master_websocket_connections_total",
"How many WebSocket connections have been initiated on the master server"
);
const wsRejectedConnectionsCounter = new prometheus.Counter(
const wsRejectedConnectionsCounter = new libPrometheus.Counter(
"clusterio_master_websocket_rejected_connections_total",
"How many WebSocket connections have been rejected during the handshake on the master server"
);
const wsActiveConnectionsGauge = new prometheus.Gauge(
const wsActiveConnectionsGauge = new libPrometheus.Gauge(
"clusterio_master_websocket_active_connections",
"How many WebSocket connections are currently open to the master server"
);
const wsActiveSlavesGauge = new prometheus.Gauge(
const wsActiveSlavesGauge = new libPrometheus.Gauge(
"clusterio_master_active_slaves",
"How many slaves are currently connected to the master"
);
@@ -137,7 +137,7 @@ async function getMetrics(req, res, next) {
endpointHitCounter.labels(req.route.path).inc();
let results = [];
let pluginResults = await plugin.invokeHook(masterPlugins, "onMetrics");
let pluginResults = await libPlugin.invokeHook(masterPlugins, "onMetrics");
for (let metricIterator of pluginResults) {
for await (let metric of metricIterator) {
results.push(metric);
@@ -147,10 +147,10 @@ async function getMetrics(req, res, next) {
let requests = [];
for (let slaveConnection of slaveConnections.values()) {
// XXX this needs a timeout
requests.push(link.messages.getMetrics.send(slaveConnection));
requests.push(libLink.messages.getMetrics.send(slaveConnection));
}
for await (let result of await prometheus.defaultRegistry.collect()) {
for await (let result of await libPrometheus.defaultRegistry.collect()) {
results.push(result);
}
@@ -169,15 +169,15 @@ async function getMetrics(req, res, next) {
}
for (let result of resultMap.values()) {
results.push(prometheus.deserializeResult(result));
results.push(libPrometheus.deserializeResult(result));
}
wsActiveConnectionsGauge.set(activeConnectors.size);
wsActiveSlavesGauge.set(slaveConnections.size);
let text = await prometheus.exposition(results);
res.set("Content-Type", prometheus.exposition.contentType);
let text = await libPrometheus.exposition(results);
res.set("Content-Type", libPrometheus.exposition.contentType);
res.send(text);
}
app.get("/metrics", (req, res, next) => getMetrics(req, res, next).catch(next));
@@ -241,7 +241,7 @@ app.put("/api/upload-export",
(req, res, next) => uploadExport(req, res, next).catch(next)
);
const masterConnectedClientsCount = new prometheus.Gauge(
const masterConnectedClientsCount = new libPrometheus.Gauge(
"clusterio_master_connected_clients_count", "How many clients are currently connected to this master server",
{
labels: ["type"], callback: async function(gauge) {
@@ -264,7 +264,7 @@ const db = {};
async function loadMap(databaseDirectory, file) {
let databasePath = path.resolve(databaseDirectory, file);
console.log(`Loading ${databasePath}`);
return await database.loadJsonArrayAsMap(databasePath);
return await libDatabase.loadJsonArrayAsMap(databasePath);
}
async function loadInstances(databaseDirectory, file) {
@@ -275,7 +275,7 @@ async function loadInstances(databaseDirectory, file) {
try {
let serialized = JSON.parse(await fs.readFile(filePath));
for (let serializedConfig of serialized) {
let instanceConfig = new config.InstanceConfig();
let instanceConfig = new libConfig.InstanceConfig();
await instanceConfig.load(serializedConfig);
let status = instanceConfig.get("instance.assigned_slave") === null ? "unassigned" : "unknown";
instances.set(instanceConfig.get("instance.id"), { config: instanceConfig, status });
@@ -296,12 +296,12 @@ async function loadUsers(databaseDirectory, file) {
try {
let content = JSON.parse(await fs.readFile(path.join(databaseDirectory, file)));
for (let serializedRole of content.roles) {
let role = new users.Role(serializedRole);
let role = new libUsers.Role(serializedRole);
loadedRoles.set(role.id, role);
}
for (let serializedUser of content.users) {
let user = new users.User(serializedUser, loadedRoles);
let user = new libUsers.User(serializedUser, loadedRoles);
loadedUsers.set(user.name, user);
}
@@ -311,8 +311,8 @@ async function loadUsers(databaseDirectory, file) {
}
// Create default roles if loading failed
users.ensureDefaultAdminRole(loadedRoles);
users.ensureDefaultPlayerRole(loadedRoles);
libUsers.ensureDefaultAdminRole(loadedRoles);
libUsers.ensureDefaultPlayerRole(loadedRoles);
}
db.roles = loadedRoles;
@@ -333,7 +333,7 @@ function createUser(name) {
}
let defaultRoleId = masterConfig.get("master.default_role_id");
let user = new users.User({ name, roles: [defaultRoleId] }, db.roles);
let user = new libUsers.User({ name, roles: [defaultRoleId] }, db.roles);
db.users.set(name, user);
return user;
}
@@ -348,7 +348,7 @@ function createUser(name) {
async function saveMap(databaseDirectory, file, map) {
let databasePath = path.resolve(databaseDirectory, file);
console.log(`Saving ${databasePath}`);
await database.saveMapAsJsonArray(databasePath, map);
await libDatabase.saveMapAsJsonArray(databasePath, map);
}
async function saveInstances(databaseDirectory, file, instances) {
@@ -394,7 +394,7 @@ async function shutdown() {
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");
await libPlugin.invokeHook(masterPlugins, "onShutdown");
stopAcceptingNewSessions = true;
@@ -418,7 +418,7 @@ async function shutdown() {
try {
await task;
} catch (err) {
if (!(err instanceof errors.SessionLost)) {
if (!(err instanceof libErrors.SessionLost)) {
console.log("Unexpected error disconnecting connector");
console.log(err);
}
@@ -455,32 +455,32 @@ async function shutdown() {
*
* @extends module:lib/link.Link
*/
class BaseConnection extends link.Link {
class BaseConnection extends libLink.Link {
constructor(target, connector) {
super("master", target, connector);
link.attachAllMessages(this);
libLink.attachAllMessages(this);
for (let masterPlugin of masterPlugins.values()) {
plugin.attachPluginMessages(this, masterPlugin.info, masterPlugin);
libPlugin.attachPluginMessages(this, masterPlugin.info, masterPlugin);
}
}
async forwardRequestToInstance(message, request) {
let instance = db.instances.get(message.data.instance_id);
if (!instance) {
throw new errors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
throw new libErrors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
}
let slaveId = instance.config.get("instance.assigned_slave");
if (slaveId === null) {
throw new errors.RequestError("Instance is not assigned to a slave");
throw new libErrors.RequestError("Instance is not assigned to a slave");
}
let connection = slaveConnections.get(slaveId);
if (!connection) {
throw new errors.RequestError("Slave containing instance is not connected");
throw new libErrors.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 libErrors.RequestError(`Slave containing instance does not have ${request.plugin} plugin`);
}
return await request.send(connection, message.data);
@@ -516,16 +516,16 @@ class BaseConnection extends link.Link {
}
async prepareDisconnectRequestHandler(message, request) {
await plugin.invokeHook(masterPlugins, "onPrepareSlaveDisconnect", this);
await libPlugin.invokeHook(masterPlugins, "onPrepareSlaveDisconnect", this);
this.connector.setClosing();
return await super.prepareDisconnectRequestHandler(message, request);
}
async disconnect(code, reason) {
try {
await link.messages.prepareDisconnect.send(this);
await libLink.messages.prepareDisconnect.send(this);
} catch (err) {
if (!(err instanceof errors.SessionLost)) {
if (!(err instanceof libErrors.SessionLost)) {
console.error("Unexpected error preparing disconnect");
console.error(err);
}
@@ -592,7 +592,7 @@ class ControlConnection extends BaseConnection {
}
async createSlaveConfigRequestHandler(message) {
let slaveConfig = new config.SlaveConfig();
let slaveConfig = new libConfig.SlaveConfig();
await slaveConfig.init();
slaveConfig.set("slave.master_url", getMasterUrl());
@@ -624,12 +624,12 @@ class ControlConnection extends BaseConnection {
// XXX should probably add a hook for slave reuqests?
async createInstanceRequestHandler(message) {
let instanceConfig = new config.InstanceConfig();
let instanceConfig = new libConfig.InstanceConfig();
await instanceConfig.load(message.data.serialized_config);
let instanceId = instanceConfig.get("instance.id");
if (db.instances.has(instanceId)) {
throw new errors.RequestError(`Instance with ID ${instanceId} already exists`);
throw new libErrors.RequestError(`Instance with ID ${instanceId} already exists`);
}
db.instances.set(instanceId, { config: instanceConfig, status: "unassigned" });
}
@@ -637,7 +637,7 @@ class ControlConnection extends BaseConnection {
async deleteInstanceRequestHandler(message, request) {
let instance = db.instances.get(message.data.instance_id);
if (!instance) {
throw new errors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
throw new libErrors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
}
if (instance.config.get("instance.assigned_slave") !== null) {
@@ -649,7 +649,7 @@ class ControlConnection extends BaseConnection {
async getInstanceConfigRequestHandler(message) {
let instance = db.instances.get(message.data.instance_id);
if (!instance) {
throw new errors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
throw new libErrors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
}
return {
@@ -662,7 +662,7 @@ class ControlConnection extends BaseConnection {
if (slaveId) {
let connection = slaveConnections.get(slaveId);
if (connection) {
await link.messages.assignInstance.send(connection, {
await libLink.messages.assignInstance.send(connection, {
instance_id: instance.config.get("instance.id"),
serialized_config: instance.config.serialize(),
});
@@ -673,16 +673,16 @@ class ControlConnection extends BaseConnection {
async setInstanceConfigFieldRequestHandler(message) {
let instance = db.instances.get(message.data.instance_id);
if (!instance) {
throw new errors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
throw new libErrors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
}
if (message.data.field === "instance.assigned_slave") {
throw new errors.RequestError("instance.assigned_slave must be set through the assign-slave interface");
throw new libErrors.RequestError("instance.assigned_slave must be set through the assign-slave interface");
}
if (message.data.field === "instance.id") {
// XXX is this worth implementing? It's race condition galore.
throw new errors.RequestError("Setting instance.id is not supported");
throw new libErrors.RequestError("Setting instance.id is not supported");
}
instance.config.set(message.data.field, message.data.value);
@@ -692,7 +692,7 @@ class ControlConnection extends BaseConnection {
async setInstanceConfigPropRequestHandler(message) {
let instance = db.instances.get(message.data.instance_id);
if (!instance) {
throw new errors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
throw new libErrors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
}
let { field, prop, value } = message.data;
@@ -703,7 +703,7 @@ class ControlConnection extends BaseConnection {
async assignInstanceCommandRequestHandler(message, request) {
let instance = db.instances.get(message.data.instance_id);
if (!instance) {
throw new errors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
throw new libErrors.RequestError(`Instance with ID ${message.data.instance_id} does not exist`);
}
// XXX: Should the instance be stopped if it's running on another slave?
@@ -715,12 +715,12 @@ class ControlConnection extends BaseConnection {
// target slave be connected to the master while doing the
// assignment, but it is IMHO a better user experience if this
// is the case.
throw new errors.RequestError("Target slave is not connected to the master server");
throw new libErrors.RequestError("Target slave is not connected to the master server");
}
instance.config.set("instance.assigned_slave", message.data.slave_id);
return await link.messages.assignInstance.send(connection, {
return await libLink.messages.assignInstance.send(connection, {
instance_id: instance.config.get("instance.id"),
serialized_config: instance.config.serialize(),
});
@@ -732,7 +732,7 @@ class ControlConnection extends BaseConnection {
async listPermissionsRequestHandler(message) {
let list = [];
for (let permission of users.permissions.values()) {
for (let permission of libUsers.permissions.values()) {
list.push({
name: permission.name,
title: permission.title,
@@ -760,7 +760,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 libUsers.Role({ id, ...message.data }));
return { id };
}
@@ -768,7 +768,7 @@ class ControlConnection extends BaseConnection {
let { id, name, description, permissions } = message.data;
let role = db.roles.get(id);
if (!role) {
throw new errors.RequestError(`Role with ID ${id} does not exist`);
throw new libErrors.RequestError(`Role with ID ${id} does not exist`);
}
role.name = name;
@@ -779,7 +779,7 @@ class ControlConnection extends BaseConnection {
async grantDefaultRolePermissionsRequestHandler(message) {
let role = db.roles.get(message.data.id);
if (!role) {
throw new errors.RequestError(`Role with ID ${message.data.id} does not exist`);
throw new libErrors.RequestError(`Role with ID ${message.data.id} does not exist`);
}
role.grantDefaultPermissions();
@@ -789,7 +789,7 @@ class ControlConnection extends BaseConnection {
let id = message.data.id;
let role = db.roles.get(id);
if (!role) {
throw new errors.RequestError(`Role with ID ${id} does not exist`);
throw new libErrors.RequestError(`Role with ID ${id} does not exist`);
}
db.roles.delete(id);
@@ -820,14 +820,14 @@ class ControlConnection extends BaseConnection {
async updateUserRolesRequestHandler(message) {
let user = db.users.get(message.data.name);
if (!user) {
throw new errors.RequestError(`User '${message.data.name}' does not exist`);
throw new libErrors.RequestError(`User '${message.data.name}' does not exist`);
}
let resolvedRoles = new Set();
for (let roleId of message.data.roles) {
let role = db.roles.get(roleId);
if (!role) {
throw new errors.RequestError(`Role with ID ${roleId} does not exist`);
throw new libErrors.RequestError(`Role with ID ${roleId} does not exist`);
}
resolvedRoles.add(role);
@@ -844,12 +844,12 @@ class ControlConnection extends BaseConnection {
this.user.checkPermission("core.user.create");
user = createUser(name);
} else {
throw new errors.RequestError(`User '${name}' does not exist`);
throw new libErrors.RequestError(`User '${name}' does not exist`);
}
}
user.isAdmin = admin;
this.broadcastEventToSlaves({ data: { name, admin }}, link.messages.adminlistUpdate);
this.broadcastEventToSlaves({ data: { name, admin }}, libLink.messages.adminlistUpdate);
}
async setUserBannedRequestHandler(message) {
@@ -860,13 +860,13 @@ class ControlConnection extends BaseConnection {
this.user.checkPermission("core.user.create");
user = createUser(name);
} else {
throw new errors.RequestError(`User '${name}' does not exist`);
throw new libErrors.RequestError(`User '${name}' does not exist`);
}
}
user.isBanned = banned;
user.banReason = reason;
this.broadcastEventToSlaves({ data: { name, banned, reason }}, link.messages.banlistUpdate);
this.broadcastEventToSlaves({ data: { name, banned, reason }}, libLink.messages.banlistUpdate);
}
async setUserWhitelistedRequestHandler(message) {
@@ -877,28 +877,28 @@ class ControlConnection extends BaseConnection {
this.user.checkPermission("core.user.create");
user = createUser(name);
} else {
throw new errors.RequestError(`User '${name}' does not exist`);
throw new libErrors.RequestError(`User '${name}' does not exist`);
}
}
user.isWhitelisted = whitelisted;
this.broadcastEventToSlaves({ data: { name, whitelisted }}, link.messages.whitelistUpdate);
this.broadcastEventToSlaves({ data: { name, whitelisted }}, libLink.messages.whitelistUpdate);
}
async deleteUserRequestHandler(message) {
let user = db.users.get(message.data.name);
if (!user) {
throw new errors.RequestError(`User '${message.data.name}' does not exist`);
throw new libErrors.RequestError(`User '${message.data.name}' does not exist`);
}
if (user.is_admin) {
this.broadcastEventToSlaves({ data: { name, admin: false }}, link.messages.adminlistUpdate);
this.broadcastEventToSlaves({ data: { name, admin: false }}, libLink.messages.adminlistUpdate);
}
if (user.is_whitelisted) {
this.broadcastEventToSlaves({ data: { name, whitelisted: false }}, link.messages.whitelistUpdate);
this.broadcastEventToSlaves({ data: { name, whitelisted: false }}, libLink.messages.whitelistUpdate);
}
if (user.is_banned) {
this.broadcastEventToSlaves({ data: { name, banned: false, reason: "" }}, link.messages.banlistUpdate);
this.broadcastEventToSlaves({ data: { name, banned: false, reason: "" }}, libLink.messages.banlistUpdate);
}
db.users.delete(message.data.name);
}
@@ -906,7 +906,7 @@ class ControlConnection extends BaseConnection {
async debugDumpWsRequestHandler(message) {
this.ws_dumper = data => {
if (this.connector.connected) {
link.messages.debugWsMessage.send(this, data);
libLink.messages.debugWsMessage.send(this, data);
}
};
this.connector._socket.clusterio_ignore_dump = true;
@@ -959,14 +959,14 @@ class SlaveConnection extends BaseConnection {
let prev = instance.status;
instance.status = message.data.status;
console.log(`Clusterio | Instance ${instance.config.get("instance.name")} State: ${instance.status}`);
await plugin.invokeHook(masterPlugins, "onInstanceStatusChanged", instance, prev);
await libPlugin.invokeHook(masterPlugins, "onInstanceStatusChanged", instance, prev);
}
async updateInstancesEventHandler(message) {
// Push updated instance configs
for (let instance of db.instances.values()) {
if (instance.config.get("instance.assigned_slave") === this._id) {
await link.messages.assignInstance.send(this, {
await libLink.messages.assignInstance.send(this, {
instance_id: instance.config.get("instance.id"),
serialized_config: instance.config.serialize(),
});
@@ -975,7 +975,7 @@ class SlaveConnection extends BaseConnection {
// Assign instances the slave has but master does not
for (let instance of message.data.instances) {
let instanceConfig = new config.InstanceConfig();
let instanceConfig = new libConfig.InstanceConfig();
await instanceConfig.load(instance.serialized_config);
let masterInstance = db.instances.get(instanceConfig.get("instance.id"));
@@ -984,7 +984,7 @@ class SlaveConnection extends BaseConnection {
if (masterInstance.status !== instance.status) {
let prev = masterInstance.status;
masterInstance.status = instance.status;
await plugin.invokeHook(masterPlugins, "onInstanceStatusChanged", instance, prev);
await libPlugin.invokeHook(masterPlugins, "onInstanceStatusChanged", instance, prev);
}
continue;
}
@@ -994,7 +994,7 @@ class SlaveConnection extends BaseConnection {
config: instanceConfig,
status: instance.status,
});
await link.messages.assignInstance.send(this, {
await libLink.messages.assignInstance.send(this, {
instance_id: instanceConfig.get("instance.id"),
serialized_config: instanceConfig.serialize(),
});
@@ -1017,14 +1017,14 @@ class SlaveConnection extends BaseConnection {
}
}
link.messages.syncUserLists.send(this, { adminlist, banlist, whitelist });
libLink.messages.syncUserLists.send(this, { adminlist, banlist, whitelist });
}
async instanceOutputEventHandler(message) {
let { instance_id, output } = message.data;
for (let controlConnection of controlConnections) {
if (controlConnection.instanceOutputSubscriptions.has(instance_id)) {
link.messages.instanceOutput.send(controlConnection, message.data);
libLink.messages.instanceOutput.send(controlConnection, message.data);
}
}
}
@@ -1043,7 +1043,7 @@ class SlaveConnection extends BaseConnection {
}
let instance = db.instances.get(instance_id);
await plugin.invokeHook(masterPlugins, "onPlayerEvent", instance, message.data);
await libPlugin.invokeHook(masterPlugins, "onPlayerEvent", instance, message.data);
}
}
@@ -1070,7 +1070,7 @@ function isInteger(value) {
*
* @extends module:lib/link.WebSocketBaseConnector
*/
class WebSocketServerConnector extends link.WebSocketBaseConnector {
class WebSocketServerConnector extends libLink.WebSocketBaseConnector {
constructor(socket, sessionId) {
super();
@@ -1290,7 +1290,7 @@ wss.on("connection", function (socket, req) {
// Start connection handshake.
socket.send(JSON.stringify({ seq: null, type: "hello", data: {
version,
plugins: pluginList,
plugins: loadedPlugins,
}}));
function attachHandler() {
@@ -1341,7 +1341,7 @@ async function handleHandshake(message, socket, req, attachHandler) {
return;
}
if (!schema.clientHandshake(message)) {
if (!libSchema.clientHandshake(message)) {
console.log(`SOCKET | closing ${req.socket.remoteAddress} after receiving invalid handshake:`, message);
wsRejectedConnectionsCounter.inc();
socket.close(1002, "Bad handshake");
@@ -1444,23 +1444,23 @@ async function handleHandshake(message, socket, req, attachHandler) {
}
// handle plugins on the master
async function pluginManagement(pluginInfos) {
async function pluginManagement() {
let startPluginLoad = Date.now();
masterPlugins = await loadPlugins(pluginInfos);
masterPlugins = await loadPlugins();
console.log("All plugins loaded in "+(Date.now() - startPluginLoad)+"ms");
}
async function loadPlugins(pluginInfos) {
async function loadPlugins() {
let plugins = new Map();
for (let pluginInfo of pluginInfos) {
if (!masterConfig.group(pluginInfo.name).get("enabled")) {
continue;
}
pluginList[pluginInfo.name] = pluginInfo.version;
loadedPlugins[pluginInfo.name] = pluginInfo.version;
let pluginLoadStarted = Date.now();
let MasterPlugin = plugin.BaseMasterPlugin;
let MasterPlugin = libPlugin.BaseMasterPlugin;
try {
if (pluginInfo.masterEntrypoint) {
({ MasterPlugin } = require(path.posix.join(pluginInfo.requirePath, pluginInfo.masterEntrypoint)));
@@ -1473,7 +1473,7 @@ async function loadPlugins(pluginInfos) {
plugins.set(pluginInfo.name, masterPlugin);
} catch (err) {
throw new errors.PluginError(pluginInfo.name, err);
throw new libErrors.PluginError(pluginInfo.name, err);
}
console.log(`Clusterio | Loaded plugin ${pluginInfo.name} in ${Date.now() - pluginLoadStarted}ms`);
@@ -1503,7 +1503,7 @@ function listen(server, ...args) {
});
function wrapError(err) {
reject(new errors.StartupError(
reject(new libErrors.StartupError(
`Server listening failed: ${err.message}`
));
}
@@ -1561,8 +1561,8 @@ async function startServer() {
default: "plugin-list.json",
type: "string",
})
.command("plugin", "Manage available plugins", sharedCommands.pluginCommand)
.command("config", "Manage Master config", config.configCommand)
.command("plugin", "Manage available plugins", libSharedCommands.pluginCommand)
.command("config", "Manage Master config", libConfig.configCommand)
.command("bootstrap", "Bootstrap access to cluster", yargs => {
yargs
.command("create-admin <name>", "Create a cluster admin")
@@ -1595,18 +1595,18 @@ async function startServer() {
// If the command is plugin management we don't try to load plugins
let command = args._[0];
if (command === "plugin") {
await sharedCommands.handlePluginCommand(args, pluginList, args.pluginList);
await libSharedCommands.handlePluginCommand(args, pluginList, args.pluginList);
return;
}
console.log("Loading Plugin info");
pluginInfos = await plugin.loadPluginInfos(pluginList);
config.registerPluginConfigGroups(pluginInfos);
config.finalizeConfigs();
pluginInfos = await libPlugin.loadPluginInfos(pluginList);
libConfig.registerPluginConfigGroups(pluginInfos);
libConfig.finalizeConfigs();
masterConfigPath = args.config;
console.log(`Loading config from ${masterConfigPath}`);
masterConfig = new config.MasterConfig();
masterConfig = new libConfig.MasterConfig();
try {
await masterConfig.load(JSON.parse(await fs.readFile(masterConfigPath)));
@@ -1629,7 +1629,7 @@ async function startServer() {
}
if (command === "config") {
await config.handleConfigCommand(args, masterConfig, masterConfigPath);
await libConfig.handleConfigCommand(args, masterConfig, masterConfigPath);
return;
} else if (command === "bootstrap") {
@@ -1647,7 +1647,7 @@ async function startServer() {
admin = createUser(args.name);
}
let adminRole = users.ensureDefaultAdminRole(db.roles);
let adminRole = libUsers.ensureDefaultAdminRole(db.roles);
admin.roles.add(adminRole);
admin.isAdmin = true;
await saveUsers(masterConfig.get("master.database_directory"), "users.json");
@@ -1659,7 +1659,7 @@ async function startServer() {
process.exitCode = 1;
return;
}
let controlConfig = new config.ControlConfig();
let controlConfig = new libConfig.ControlConfig();
await controlConfig.init();
controlConfig.set("control.master_url", getMasterUrl());
@@ -1728,7 +1728,7 @@ async function startServer() {
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({
await generateSslCert({
bits: masterConfig.get("master.tls_bits"),
sslCertPath: tls_cert,
sslPrivKeyPath: tls_key,
@@ -1747,11 +1747,11 @@ async function startServer() {
}
// Load plugins
await pluginManagement(pluginInfos);
await pluginManagement();
// Only start listening for connections after all plugins have loaded
if (httpPort) {
httpServer = require("http").Server(app);
httpServer = new (require("http").Server)(app);
await listen(httpServer, httpPort);
console.log("Listening for HTTP on port %s...", httpServer.address().port);
}
@@ -1763,7 +1763,7 @@ async function startServer() {
privateKey = await fs.readFile(tls_key);
} catch (err) {
throw new errors.StartupError(
throw new libErrors.StartupError(
`Error loading ssl certificate: ${err.message}`
);
}
@@ -1799,13 +1799,13 @@ I version of clusterio. Expect things to break. I
`
);
startServer().catch(err => {
if (err instanceof errors.StartupError) {
if (err instanceof libErrors.StartupError) {
console.error(`
+----------------------------------+
| Unable to to start master server |
+----------------------------------+`
);
} else if (err instanceof errors.PluginError) {
} else if (err instanceof libErrors.PluginError) {
console.error(`
Error: ${err.pluginName} plugin threw an unexpected error
during startup, please report it to the plugin author.
@@ -56,6 +56,7 @@ class DataTable extends Component {
sorter = sorter || this.state.sorter;
// console.log(pagination, filters, sorter)
// eslint-disable-next-line new-cap
let data = await this.props.DataFunction();
// await fetchApi(this.state.dataUrl, "post", {
// database: this.props.Database,
@@ -69,6 +70,7 @@ class DataTable extends Component {
console.log(data);
data = data || {};
this.setState({
// eslint-disable-next-line new-cap
tableData: this.props.CustomDataFilterFunction ? this.props.CustomDataFilterFunction(data) : data,
pagination: { ...pagination, ...data.pagination },
sorter,
@@ -200,8 +200,10 @@ export class InstanceView extends Component {
this.getData();
}}
>
{this.state.slaves.map(slave => <Option defaultValue={instance.assigned_slave} value={slave.id} key={slave.id}>
{slave.name}
{this.state.slaves.map(newSlave => <Option
defaultValue={instance.assigned_slave} value={newSlave.id} key={newSlave.id}
>
{newSlave.name}
</Option>)}
</Select>
}
+9 -9
View File
@@ -1,6 +1,6 @@
const link = require("@clusterio/lib/link");
const plugin = require("@clusterio/lib/plugin");
const errors = require("@clusterio/lib/errors");
const libLink = require("@clusterio/lib/link");
const libPlugin = require("@clusterio/lib/plugin");
const libErrors = require("@clusterio/lib/errors");
/**
* Format a parsed Factorio output message
@@ -35,7 +35,7 @@ function formatOutput(output) {
* Connector for control connection to master server
* @private
*/
export class ControlConnector extends link.WebSocketClientConnector {
export class ControlConnector extends libLink.WebSocketClientConnector {
constructor(url, reconnectDelay, token) {
super(url, reconnectDelay);
this._token = token;
@@ -57,10 +57,10 @@ export class ControlConnector extends link.WebSocketClientConnector {
* Connects to the master server over WebSocket and sends commands to it.
* @static
*/
export class Control extends link.Link {
export class Control extends libLink.Link {
constructor(connector, controlPlugins) {
super("control", "master", connector);
link.attachAllMessages(this);
libLink.attachAllMessages(this);
/**
* Mapping of plugin names to their instance for loaded plugins.
@@ -68,7 +68,7 @@ export class Control extends link.Link {
*/
this.plugins = controlPlugins;
for (let controlPlugin of controlPlugins.values()) {
plugin.attachPluginMessages(this, controlPlugin.info, controlPlugin);
libPlugin.attachPluginMessages(this, controlPlugin.info, controlPlugin);
}
}
@@ -88,9 +88,9 @@ export class Control extends link.Link {
this.connector.setTimeout(30);
try {
await link.messages.prepareDisconnect.send(this);
await libLink.messages.prepareDisconnect.send(this);
} catch (err) {
if (!(err instanceof errors.SessionLost)) {
if (!(err instanceof libErrors.SessionLost)) {
throw err;
}
}
+93 -91
View File
@@ -24,36 +24,36 @@ const util = require("util");
const version = require("./package").version;
// internal libraries
const fileOps = require("@clusterio/lib/fileOps");
const factorio = require("@clusterio/lib/factorio");
const link = require("@clusterio/lib/link");
const plugin = require("@clusterio/lib/plugin");
const errors = require("@clusterio/lib/errors");
const prometheus = require("@clusterio/lib/prometheus");
const luaTools = require("@clusterio/lib/luaTools");
const config = require("@clusterio/lib/config");
const sharedCommands = require("@clusterio/lib/shared_commands");
const libFileOps = require("@clusterio/lib/file_ops");
const libFactorio = require("@clusterio/lib/factorio");
const libLink = require("@clusterio/lib/link");
const libPlugin = require("@clusterio/lib/plugin");
const libErrors = require("@clusterio/lib/errors");
const libPrometheus = require("@clusterio/lib/prometheus");
const libLuaTools = require("@clusterio/lib/lua_tools");
const libConfig = require("@clusterio/lib/config");
const libSharedCommands = require("@clusterio/lib/shared_commands");
const instanceRconCommandsCounter = new prometheus.Counter(
const instanceRconCommandsCounter = new libPrometheus.Counter(
"clusterio_instance_rcon_commands_total",
"How many commands have been sent to the instance",
{ labels: ["instance_id"] }
);
const instanceFactorioCpuTime = new prometheus.Gauge(
const instanceFactorioCpuTime = new libPrometheus.Gauge(
"clusterio_instance_factorio_cpu_time_total",
"Factorio CPU time spent in seconds.",
{ labels: ["instance_id"] }
);
const instanceFactorioMemoryUsage = new prometheus.Gauge(
const instanceFactorioMemoryUsage = new libPrometheus.Gauge(
"clusterio_instance_factorio_resident_memory_bytes",
"Factorio resident memory size in bytes.",
{ labels: ["instance_id"] }
);
const instanceFactorioAutosaveSize = new prometheus.Gauge(
const instanceFactorioAutosaveSize = new libPrometheus.Gauge(
"clusterio_instance_factorio_autosave_bytes",
"Size of Factorio server autosave in bytes.",
{ labels: ["instance_id"] }
@@ -105,10 +105,10 @@ const serverSettingsActions = {
/**
* Keeps track of the runtime parameters of an instance
*/
class Instance extends link.Link{
class Instance extends libLink.Link{
constructor(slave, connector, dir, factorioDir, instanceConfig) {
super("instance", "slave", connector);
link.attachAllMessages(this);
libLink.attachAllMessages(this);
this._slave = slave;
this._dir = dir;
@@ -116,7 +116,7 @@ class Instance extends link.Link{
this.config = instanceConfig;
this._configFieldChanged = (group, field, prev) => {
let hook = () => plugin.invokeHook(this.plugins, "onInstanceConfigFieldChanged", group, field, prev);
let hook = () => libPlugin.invokeHook(this.plugins, "onInstanceConfigFieldChanged", group, field, prev);
if (group.name === "factorio" && field === "settings") {
this.updateFactorioSettings(group.get(field), prev).finally(hook);
@@ -137,7 +137,7 @@ class Instance extends link.Link{
};
this._running = false;
this.server = new factorio.FactorioServer(
this.server = new libFactorio.FactorioServer(
factorioDir, this._dir, serverOptions
);
@@ -148,9 +148,9 @@ class Instance extends link.Link{
};
this.server.on("output", (output) => {
link.messages.instanceOutput.send(this, { instance_id: this.config.get("instance.id"), output });
libLink.messages.instanceOutput.send(this, { instance_id: this.config.get("instance.id"), output });
plugin.invokeHook(this.plugins, "onOutput", output);
libPlugin.invokeHook(this.plugins, "onOutput", output);
});
this.server.on("error", err => {
@@ -164,11 +164,11 @@ class Instance extends link.Link{
});
this.server.on("ipc-player_event", event => {
link.messages.playerEvent.send(this, {
libLink.messages.playerEvent.send(this, {
instance_id: this.config.get("instance.id"),
...event,
});
plugin.invokeHook(this.plugins, "onPlayerEvent", event);
libPlugin.invokeHook(this.plugins, "onPlayerEvent", event);
});
}
@@ -178,7 +178,7 @@ class Instance extends link.Link{
}
notifyStatus(status) {
link.messages.instanceStatusChanged.send(this, {
libLink.messages.instanceStatusChanged.send(this, {
instance_id: this.config.get("instance.id"), status,
});
}
@@ -190,9 +190,9 @@ class Instance extends link.Link{
this.config.off("fieldChanged", this._configFieldChanged);
// Clear metrics this instance is exporting
for (let collector of prometheus.defaultRegistry.collectors) {
for (let collector of libPrometheus.defaultRegistry.collectors) {
if (
collector instanceof prometheus.ValueCollector
collector instanceof libPrometheus.ValueCollector
&& collector.metric.labels.includes("instance_id")
) {
collector.removeAll({ instance_id: String(this.config.get("instance.id")) });
@@ -211,7 +211,7 @@ class Instance extends link.Link{
let instancePlugin = new InstancePlugin(pluginInfo, this, slave);
this.plugins.set(pluginInfo.name, instancePlugin);
await instancePlugin.init();
plugin.attachPluginMessages(this, pluginInfo, instancePlugin);
libPlugin.attachPluginMessages(this, pluginInfo, instancePlugin);
console.log(`Clusterio | Loaded plugin ${pluginInfo.name} in ${Date.now() - pluginLoadStarted}ms`);
}
@@ -241,7 +241,7 @@ class Instance extends link.Link{
for (let [name, plugin] of this.plugins) {
plugins[name] = plugin.info.version;
}
link.messages.instanceInitialized.send(this, { instance_id: this.config.get("instance.id"), plugins });
libLink.messages.instanceInitialized.send(this, { instance_id: this.config.get("instance.id"), plugins });
}
/**
@@ -362,7 +362,7 @@ class Instance extends link.Link{
async prepareSave(saveName) {
// Use latest save if no save was specified
if (saveName === null) {
saveName = await fileOps.getNewestFile(
saveName = await libFileOps.getNewestFile(
this.path("saves"), (name) => !name.endsWith(".tmp.zip")
);
}
@@ -440,7 +440,7 @@ class Instance extends link.Link{
}
}
await factorio.patch(this.path("saves", saveName), [...modules.values()]);
await libFactorio.patch(this.path("saves", saveName), [...modules.values()]);
return saveName;
}
@@ -464,7 +464,7 @@ class Instance extends link.Link{
await this.updateInstanceData();
}
await plugin.invokeHook(this.plugins, "onStart");
await libPlugin.invokeHook(this.plugins, "onStart");
this._running = true;
this.notifyStatus("running");
@@ -486,7 +486,7 @@ class Instance extends link.Link{
this.server.on("exit", () => this.notifyExit());
await this.server.startScenario(scenario);
await plugin.invokeHook(this.plugins, "onStart");
await libPlugin.invokeHook(this.plugins, "onStart");
this._running = true;
this.notifyStatus("running");
@@ -496,7 +496,7 @@ class Instance extends link.Link{
* Update instance information on the Factorio side
*/
async updateInstanceData() {
let name = luaTools.escapeString(this.config.get("instance.name"));
let name = libLuaTools.escapeString(this.config.get("instance.name"));
let id = this.config.get("instance.id");
await this.server.sendRcon(`/sc clusterio_private.update_instance(${id}, "${name}")`, true);
}
@@ -574,26 +574,26 @@ class Instance extends link.Link{
// XXX this needs more thought to it
if (this.server._state === "running") {
await plugin.invokeHook(this.plugins, "onStop");
await libPlugin.invokeHook(this.plugins, "onStop");
await this.server.stop();
}
}
async masterConnectionEventEventHandler(message) {
await plugin.invokeHook(this.plugins, "onMasterConnectionEvent", message.data.event);
await libPlugin.invokeHook(this.plugins, "onMasterConnectionEvent", message.data.event);
}
async prepareMasterDisconnectRequestHandler() {
await plugin.invokeHook(this.plugins, "onPrepareMasterDisconnect");
await libPlugin.invokeHook(this.plugins, "onPrepareMasterDisconnect");
}
async getMetricsRequestHandler() {
let results = [];
if (this._running) {
let pluginResults = await plugin.invokeHook(this.plugins, "onMetrics");
let pluginResults = await libPlugin.invokeHook(this.plugins, "onMetrics");
for (let metricIterator of pluginResults) {
for await (let metric of metricIterator) {
results.push(prometheus.serializeResult(metric));
results.push(libPrometheus.serializeResult(metric));
}
}
}
@@ -629,7 +629,7 @@ class Instance extends link.Link{
async loadScenarioRequestHandler(message) {
if (this.config.get("factorio.enable_save_patching")) {
this.notifyExit();
throw new errors.RequestError("Load scenario cannot be used with save patching enabled");
throw new libErrors.RequestError("Load scenario cannot be used with save patching enabled");
}
try {
@@ -674,7 +674,7 @@ class Instance extends link.Link{
console.log("Exporting data .....");
await symlinkMods(this, "sharedMods", console);
let zip = await factorio.exportData(this.server);
let zip = await libFactorio.exportData(this.server);
let content = await zip.generateAsync({ type: "nodebuffer" });
let url = new URL(this._slave.config.get("slave.master_url"));
@@ -745,7 +745,7 @@ class Instance extends link.Link{
async function discoverInstances(instanceInfos, instancesDir, logger) {
for (let entry of await fs.readdir(instancesDir, { withFileTypes: true })) {
if (entry.isDirectory()) {
let instanceConfig = new config.InstanceConfig();
let instanceConfig = new libConfig.InstanceConfig();
let configPath = path.join(instancesDir, entry.name, "instance.json");
try {
@@ -775,17 +775,17 @@ async function discoverInstances(instanceInfos, instancesDir, logger) {
}
}
class InstanceConnection extends link.Link {
class InstanceConnection extends libLink.Link {
constructor(connector, slave, instanceId) {
super("slave", "instance", connector);
this.slave = slave;
this.instanceId = instanceId;
this.plugins = new Map();
this.status = "stopped";
link.attachAllMessages(this);
libLink.attachAllMessages(this);
for (let pluginInfo of slave.pluginInfos) {
plugin.attachPluginMessages(this, pluginInfo, null);
libPlugin.attachPluginMessages(this, pluginInfo, null);
}
}
@@ -833,7 +833,7 @@ class InstanceConnection extends link.Link {
}
}
class SlaveConnector extends link.WebSocketClientConnector {
class SlaveConnector extends libLink.WebSocketClientConnector {
constructor(slaveConfig, pluginInfos) {
super(slaveConfig.get("slave.master_url"), slaveConfig.get("slave.reconnect_delay"));
this.slaveConfig = slaveConfig;
@@ -863,16 +863,16 @@ class SlaveConnector extends link.WebSocketClientConnector {
*
* Connects to the master server over the WebSocket and manages intsances.
*/
class Slave extends link.Link {
class Slave extends libLink.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);
link.attachAllMessages(this);
libLink.attachAllMessages(this);
this.pluginInfos = pluginInfos;
for (let pluginInfo of pluginInfos) {
plugin.attachPluginMessages(this, pluginInfo, null);
libPlugin.attachPluginMessages(this, pluginInfo, null);
}
this.config = slaveConfig;
@@ -900,12 +900,13 @@ class Slave extends link.Link {
this.updateInstances().catch((err) => {
console.error("ERROR: Unexpected error updating instances");
console.error(err);
this.shutdown().catch((err) => {
setBlocking(true);
console.error("ERROR: Unexpected error during shutdown");
console.error(err);
process.exit(1);
});
return this.shutdown();
}).catch((err) => {
setBlocking(true);
console.error("ERROR: Unexpected error during shutdown");
console.error(err);
process.exit(1);
});
});
@@ -919,12 +920,13 @@ class Slave extends link.Link {
this.connector.connect().catch((err) => {
console.error("ERROR: Unexpected error reconnecting to master");
console.error(err);
this.shutdown().catch((err) => {
setBlocking(true);
console.error("ERROR: Unexpected error during shutdown");
console.error(err);
process.exit(1);
});
return this.shutdown();
}).catch((err) => {
setBlocking(true);
console.error("ERROR: Unexpected error during shutdown");
console.error(err);
process.exit(1);
});
} else {
@@ -941,7 +943,7 @@ class Slave extends link.Link {
for (let event of ["connect", "drop", "close"]) {
this.connector.on(event, () => {
for (let instanceConnection of this.instanceConnections.values()) {
link.messages.masterConnectionEvent.send(instanceConnection, { event });
libLink.messages.masterConnectionEvent.send(instanceConnection, { event });
}
});
}
@@ -966,16 +968,16 @@ class Slave extends link.Link {
async forwardRequestToInstance(message, request) {
let instanceId = message.data.instance_id;
if (!this.instanceInfos.has(instanceId)) {
throw new errors.RequestError(`Instance with ID ${instanceId} does not exist`);
throw new libErrors.RequestError(`Instance with ID ${instanceId} does not exist`);
}
let instanceConnection = this.instanceConnections.get(instanceId);
if (!instanceConnection) {
throw new errors.RequestError(`Instance ID ${instanceId} is not running`);
throw new libErrors.RequestError(`Instance ID ${instanceId} is not running`);
}
if (request.plugin && !instanceConnection.plugins.has(request.plugin)) {
throw new errors.RequestError(`Instance ID ${instanceId} does not have ${request.plugin} plugin loaded`);
throw new libErrors.RequestError(`Instance ID ${instanceId} does not have ${request.plugin} plugin loaded`);
}
return await request.send(instanceConnection, message.data);
@@ -1022,8 +1024,8 @@ class Slave extends link.Link {
}
};
updateList(this.adminlist, message.data.adminlist, "admin", link.messages.adminlistUpdate);
updateList(this.whitelist, message.data.whitelist, "whitelisted", link.messages.whitelistUpdate);
updateList(this.adminlist, message.data.adminlist, "admin", libLink.messages.adminlistUpdate);
updateList(this.whitelist, message.data.whitelist, "whitelisted", libLink.messages.whitelistUpdate);
let updatedBanlist = new Map(message.data.banlist);
let addedOrChanged = new Map(updatedBanlist);
@@ -1038,14 +1040,14 @@ class Slave extends link.Link {
for (let [name, reason] of addedOrChanged) {
this.banlist.set(name, reason);
this.broadcastEventToInstance(
{ data: { name, banned: true, reason }}, link.messages.banlistUpdate
{ data: { name, banned: true, reason }}, libLink.messages.banlistUpdate
);
}
for (let name of removed) {
this.banlist.delete(name);
this.broadcastEventToInstance(
{ data: { name, banned: false, reason: "" }}, link.messages.banlistUpdate
{ data: { name, banned: false, reason: "" }}, libLink.messages.banlistUpdate
);
}
}
@@ -1086,7 +1088,7 @@ class Slave extends link.Link {
// TODO: Notify of update if instance is running
} else {
let instanceConfig = new config.InstanceConfig();
let instanceConfig = new libConfig.InstanceConfig();
await instanceConfig.load(serialized_config);
// XXX: race condition on multiple simultanious calls
@@ -1113,7 +1115,7 @@ class Slave extends link.Link {
);
// Notify master the instance is no longer usassigned
link.messages.instanceStatusChanged.send(this, { instance_id, status: "stopped" });
libLink.messages.instanceStatusChanged.send(this, { instance_id, status: "stopped" });
}
/**
@@ -1125,14 +1127,14 @@ class Slave extends link.Link {
async _connectInstance(instanceId) {
let instanceInfo = this.instanceInfos.get(instanceId);
if (!instanceInfo) {
throw new errors.RequestError(`Instance with ID ${instanceId} does not exist`);
throw new libErrors.RequestError(`Instance with ID ${instanceId} does not exist`);
}
if (this.instanceConnections.has(instanceId)) {
throw new errors.RequestError(`Instance with ID ${instanceId} is running`);
throw new libErrors.RequestError(`Instance with ID ${instanceId} is running`);
}
let [connectionClient, connectionServer] = link.VirtualConnector.makePair();
let [connectionClient, connectionServer] = libLink.VirtualConnector.makePair();
let instanceConnection = new InstanceConnection(connectionServer, this, instanceId);
let instance = new Instance(
this, connectionClient, instanceInfo.path, this.config.get("slave.factorio_directory"), instanceInfo.config
@@ -1147,7 +1149,7 @@ class Slave extends link.Link {
async getMetricsRequestHandler() {
let requests = [];
for (let instanceConnection of this.instanceConnections.values()) {
requests.push(link.messages.getMetrics.send(instanceConnection));
requests.push(libLink.messages.getMetrics.send(instanceConnection));
}
let results = [];
@@ -1155,15 +1157,15 @@ class Slave extends link.Link {
results.push(...response.results);
}
for await (let result of prometheus.defaultRegistry.collect()) {
for await (let result of libPrometheus.defaultRegistry.collect()) {
if (result.metric.name.startsWith("process_")) {
results.push(prometheus.serializeResult(result, {
results.push(libPrometheus.serializeResult(result, {
addLabels: { "slave_id": String(this.config.get("slave.id")) },
metricName: result.metric.name.replace("process_", "clusterio_slave_"),
}));
} else {
results.push(prometheus.serializeResult(result));
results.push(libPrometheus.serializeResult(result));
}
}
@@ -1196,18 +1198,18 @@ class Slave extends link.Link {
async stopInstance(instanceId) {
let instanceConnection = this.instanceConnections.get(instanceId);
await link.messages.stopInstance.send(instanceConnection, { instance_id: instanceId });
await libLink.messages.stopInstance.send(instanceConnection, { instance_id: instanceId });
}
async deleteInstanceRequestHandler(message) {
let instanceId = message.data.instance_id;
if (this.instanceConnections.has(instanceId)) {
throw new errors.RequestError(`Instance with ID ${instanceId} is running`);
throw new libErrors.RequestError(`Instance with ID ${instanceId} is running`);
}
let instanceInfo = this.instanceInfos.get(instanceId);
if (!instanceInfo) {
throw new errors.RequestError(`Instance with ID ${instanceId} does not exist`);
throw new libErrors.RequestError(`Instance with ID ${instanceId} does not exist`);
}
await fs.remove(instanceInfo.path);
@@ -1230,13 +1232,13 @@ class Slave extends link.Link {
status: instanceConnection ? instanceConnection.status : "stopped",
});
}
link.messages.updateInstances.send(this, { instances: list });
libLink.messages.updateInstances.send(this, { instances: list });
}
async prepareDisconnectRequestHandler(message, request) {
this._disconnecting = true;
for (let instanceConnection of this.instanceConnections.values()) {
await link.messages.prepareMasterDisconnect.send(instanceConnection);
await libLink.messages.prepareMasterDisconnect.send(instanceConnection);
}
this.connector.setClosing();
return await super.prepareDisconnectRequestHandler(message, request);
@@ -1250,9 +1252,9 @@ class Slave extends link.Link {
this.connector.setTimeout(30);
try {
await link.messages.prepareDisconnect.send(this);
await libLink.messages.prepareDisconnect.send(this);
} catch (err) {
if (!(err instanceof errors.SessionLost)) {
if (!(err instanceof libErrors.SessionLost)) {
console.error("Unexpected error preparing disconnect");
console.error(err);
}
@@ -1391,8 +1393,8 @@ async function startSlave() {
default: "plugin-list.json",
type: "string",
})
.command("plugin", "Manage available plugins", sharedCommands.pluginCommand)
.command("config", "Manage Slave config", config.configCommand)
.command("plugin", "Manage available plugins", libSharedCommands.pluginCommand)
.command("config", "Manage Slave config", libConfig.configCommand)
.command("run", "Run slave")
.demandCommand(1, "You need to specify a command to run")
.strict()
@@ -1412,17 +1414,17 @@ async function startSlave() {
// If the command is plugin management we don't try to load plugins
let command = args._[0];
if (command === "plugin") {
await sharedCommands.handlePluginCommand(args, pluginList, args.pluginList);
await libSharedCommands.handlePluginCommand(args, pluginList, args.pluginList);
return;
}
console.log("Loading Plugin info");
let pluginInfos = await plugin.loadPluginInfos(pluginList);
config.registerPluginConfigGroups(pluginInfos);
config.finalizeConfigs();
let pluginInfos = await libPlugin.loadPluginInfos(pluginList);
libConfig.registerPluginConfigGroups(pluginInfos);
libConfig.finalizeConfigs();
console.log(`Loading config from ${args.config}`);
let slaveConfig = new config.SlaveConfig();
let slaveConfig = new libConfig.SlaveConfig();
try {
await slaveConfig.load(JSON.parse(await fs.readFile(args.config)));
@@ -1437,7 +1439,7 @@ async function startSlave() {
}
if (command === "config") {
await config.handleConfigCommand(args, slaveConfig, args.config);
await libConfig.handleConfigCommand(args, slaveConfig, args.config);
return;
}
@@ -1518,7 +1520,7 @@ I version of clusterio. Expect things to break. I
`
);
startSlave().catch(err => {
if (err instanceof errors.AuthenticationFailed) {
if (err instanceof libErrors.AuthenticationFailed) {
console.error(err.message);
} else {
+7 -5
View File
@@ -1,11 +1,13 @@
"use strict";
const plugin = require("@clusterio/lib/plugin");
const libPlugin = require("@clusterio/lib/plugin");
const info = require("./info");
const command = require("@clusterio/lib/command");
const libCommand = require("@clusterio/lib/command");
const globalChatCommands = new command.CommandTree({ name: "global-chat", description: "Global Chat plugin commands" });
globalChatCommands.add(new command.Command({
const globalChatCommands = new libCommand.CommandTree({
name: "global-chat", description: "Global Chat plugin commands",
});
globalChatCommands.add(new libCommand.Command({
definition: ["shout <message>", "Send message to all instances", (yargs) => {
yargs.positional("message", { describe: "message to send", type: "string" });
}],
@@ -17,7 +19,7 @@ globalChatCommands.add(new command.Command({
},
}));
class ControlPlugin extends plugin.BaseControlPlugin {
class ControlPlugin extends libPlugin.BaseControlPlugin {
async addCommands(rootCommand) {
rootCommand.add(globalChatCommands);
}
+2 -3
View File
@@ -1,6 +1,5 @@
"use strict";
let link = require("@clusterio/lib/link");
let plugin = require("@clusterio/lib/plugin");
let libLink = require("@clusterio/lib/link");
module.exports = {
name: "global_chat",
@@ -10,7 +9,7 @@ module.exports = {
controlEntrypoint: "control",
messages: {
chat: new link.Event({
chat: new libLink.Event({
type: "global_chat:chat",
links: ["instance-slave", "slave-master", "master-slave", "slave-instance", "control-master"],
forwardTo: "master",
+4 -4
View File
@@ -2,8 +2,8 @@
* @module
*/
"use strict";
const plugin = require("@clusterio/lib/plugin");
const luaTools = require("@clusterio/lib/luaTools");
const libPlugin = require("@clusterio/lib/plugin");
const libLuaTools = require("@clusterio/lib/lua_tools");
/**
@@ -16,7 +16,7 @@ function removeTags(content) {
return content.replace(/(\[gps=-?\d+,-?\d+\]|\[train=\d+\])/g, "");
}
class InstancePlugin extends plugin.BaseInstancePlugin {
class InstancePlugin extends libPlugin.BaseInstancePlugin {
async init() {
this.messageQueue = [];
}
@@ -33,7 +33,7 @@ class InstancePlugin extends plugin.BaseInstancePlugin {
async chatEventHandler(message) {
// TODO check if cross server chat is enabled
let content = `[${message.data.instance_name}] ${removeTags(message.data.content)}`;
await this.instance.server.sendRcon(`/sc game.print('${luaTools.escapeString(content)}')`, true);
await this.instance.server.sendRcon(`/sc game.print('${libLuaTools.escapeString(content)}')`, true);
}
sendChat(message) {
+5 -5
View File
@@ -1,5 +1,5 @@
"use strict";
const link = require("@clusterio/lib/link");
const libLink = require("@clusterio/lib/link");
// schema used for syncing technologies to and from the master
@@ -26,7 +26,7 @@ module.exports = {
masterEntrypoint: "master",
messages: {
contribution: new link.Event({
contribution: new libLink.Event({
type: "research_sync:contribution",
links: ["instance-slave", "slave-master"],
forwardTo: "master",
@@ -36,7 +36,7 @@ module.exports = {
"contribution": { type: "number" },
},
}),
progress: new link.Event({
progress: new libLink.Event({
type: "research_sync:progress",
links: ["master-slave", "slave-instance"],
broadcastTo: "instance",
@@ -55,7 +55,7 @@ module.exports = {
},
},
}),
finished: new link.Event({
finished: new libLink.Event({
type: "research_sync:finished",
links: ["instance-slave", "slave-master", "master-slave", "slave-instance"],
forwardTo: "master",
@@ -65,7 +65,7 @@ module.exports = {
"level": { type: "integer" },
},
}),
syncTechnologies: new link.Request({
syncTechnologies: new libLink.Request({
type: "research_sync:sync_technologies",
links: ["instance-slave", "slave-master"],
forwardTo: "master",
+6 -6
View File
@@ -1,6 +1,6 @@
"use strict";
const plugin = require("@clusterio/lib/plugin");
const luaTools = require("@clusterio/lib/luaTools");
const libPlugin = require("@clusterio/lib/plugin");
const libLuaTools = require("@clusterio/lib/lua_tools");
function unexpectedError(err) {
@@ -9,7 +9,7 @@ function unexpectedError(err) {
console.log(err);
}
class InstancePlugin extends plugin.BaseInstancePlugin {
class InstancePlugin extends libPlugin.BaseInstancePlugin {
async init() {
if (!this.instance.config.get("factorio.enable_save_patching")) {
throw new Error("research_sync plugin requires save patching.");
@@ -28,7 +28,7 @@ class InstancePlugin extends plugin.BaseInstancePlugin {
}
async progressEventHandler(message) {
let techsJson = luaTools.escapeString(JSON.stringify(message.data.technologies));
let techsJson = libLuaTools.escapeString(JSON.stringify(message.data.technologies));
await this.instance.server.sendRcon(`/sc research_sync.update_progress("${techsJson}")`, true);
}
@@ -39,7 +39,7 @@ class InstancePlugin extends plugin.BaseInstancePlugin {
async finishedEventHandler(message) {
let { name, level, researched } = message.data;
await this.instance.server.sendRcon(
`/sc research_sync.research_technology("${luaTools.escapeString(name)}", ${level})`, true
`/sc research_sync.research_technology("${libLuaTools.escapeString(name)}", ${level})`, true
);
}
@@ -73,7 +73,7 @@ class InstancePlugin extends plugin.BaseInstancePlugin {
}
if (techsToSync.length) {
let syncJson = luaTools.escapeString(JSON.stringify(techsToSync));
let syncJson = libLuaTools.escapeString(JSON.stringify(techsToSync));
await this.instance.server.sendRcon(`/sc research_sync.sync_technologies("${syncJson}")`, true);
}
}
+3 -3
View File
@@ -2,10 +2,10 @@
const fs = require("fs-extra");
const path = require("path");
const plugin = require("@clusterio/lib/plugin");
const libPlugin = require("@clusterio/lib/plugin");
class MasterPlugin extends plugin.BaseMasterPlugin {
class MasterPlugin extends libPlugin.BaseMasterPlugin {
async init() {
this.technologies = await loadTechnologies(this.master.config);
this.lastProgressBroadcast = Date.now();
@@ -118,7 +118,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin {
this.technologies.set(name, { level, progress, researched });
if (progress) {
this.progressToBroadcast.add(name);
} else if (researched || baseLevel(name) != level) {
} else if (researched || baseLevel(name) !== level) {
this.broadcastEventToSlaves(this.info.messages.finished, { name, level });
}
-1
View File
@@ -1,5 +1,4 @@
"use strict";
let link = require("@clusterio/lib/link");
module.exports = {
name: "statistics_exporter",
+2 -2
View File
@@ -1,5 +1,5 @@
"use strict";
const plugin = require("@clusterio/lib/plugin");
const libPlugin = require("@clusterio/lib/plugin");
const { Gauge } = require("@clusterio/lib/prometheus");
@@ -21,7 +21,7 @@ const instanceGameFlowStatistics = new Gauge(
);
class InstancePlugin extends plugin.BaseInstancePlugin {
class InstancePlugin extends libPlugin.BaseInstancePlugin {
async init() {
if (!this.instance.config.get("factorio.enable_save_patching")) {
throw new Error("statistics_exporter plugin requires save patching.");
+66 -51
View File
@@ -1,37 +1,40 @@
"use strict";
const doleNN = require("./dole_nn_base");
const prometheus = require("@clusterio/lib/prometheus");
const { Gauge } = require("@clusterio/lib/prometheus");
const prometheusNNDoleGauge = new prometheus.Gauge(
const prometheusNNDoleGauge = new Gauge(
"clusterio_subspace_storage_nn_dole_gauge",
"Current demand being supplied by Neural Network ; 1 means all demand covered, 0.5 means about half of each supply is covered, 0 means no items are given",
"Current demand being supplied by Neural Network ; 1 means all demand covered, "+
"0.5 means about half of each supply is covered, 0 means no items are given",
{ labels: ["resource"] }
);
const prometheusDoleFactorGauge = new prometheus.Gauge(
const prometheusDoleFactorGauge = new Gauge(
"clusterio_subspace_storage_dole_factor_gauge",
"The current dole division factor for this resource",
{ labels: ["resource"] }
);
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;
class NeuralDole {
getRequestStats(itemname, samples) {
let sum = 0;
samples = Math.min(samples, (this.stats[itemname] || []).length - 1);
if (samples < 1) {
return 0.1;
}
if (sum==0) return 0.1;
return sum/samples;
for (let 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);
@@ -44,15 +47,17 @@ class neuralDole {
doMagic() {
for (let [name, count] of this.items._items) {
let magicData = doleNN.Tick(
let magicData = doleNN.tick(
count,
this.dole[name],
this.itemsLastTick.get(name),
this.getRequestStats(name,5)
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
if (this.stats[name].length>10) this.stats[name].pop();//remove if too many samples in stats
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];
// DONE handle magicData[1] for graphing for our users
@@ -62,28 +67,27 @@ class neuralDole {
}
divider(object) {
let magicData = doleNN.Dose(
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.debt[object.name+" "+object.instanceID] || 0
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]);
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.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];
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]);
@@ -92,42 +96,53 @@ class neuralDole {
}
}
// If the server regularly can't fulfill requests, this number grows until it can. Then it slowly shrinks back down.
// 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,
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
// lower rates will equal more dramatic swings
const doleDivisionRetardation = 10;
// a higher cap will divide the store more ways, but will take longer to
// recover as supplies increase
const maxDoleDivision = 250;
const originalCount = Number(object.count) || 0;
object.count /= ((_doleDivisionFactor[object.name]||0)+doleDivisionRetardation)/doleDivisionRetardation;
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;
// 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+")");
}
// 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,
NeuralDole,
};
+125 -91
View File
@@ -1,120 +1,154 @@
//Sigmoids...
// Sigmoids...
"use strict";
function sigmoid0(x)
{
return x/(1+Math.abs(x));
function sigmoid0(x) {
return x / (1 + Math.abs(x));
}
function sigmoid1(x)
{
if (x>=0.0) {
function sigmoid1(x) {
if (x >= 0.0) {
return 1.0;
} else {
return 0.0;
}
return 0.0;
}
function sigmoid2(x)
{
if (x>=0.0){
function sigmoid2(x) {
if (x >= 0.0) {
return x;
} else {
return 0.0;
}
return 0.0;
}
function sigmoid3(x)
{
function sigmoid3(x) {
return x;
}
//DOSE - Doses items for item X slave Y
//Numreq - number of items that are requested for item X for slave Y
//Instore - number of items for item X in master gauge (currently)
//store_last_tick - Number of items for item X in master gauge at time of last tick
//dole - Current dole for item X
//carry - carry for item X slave Y
//prev_req - Numreq for previous request for item X slave Y
//* doleinfo_last - Object with 2 values for last "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item
//debt - debt for item X slave Y
// DOSE - Doses items for item X slave Y
// Numreq - number of items that are requested for item X for slave Y
// Instore - number of items for item X in master gauge (currently)
// store_last_tick -
// Number of items for item X in master gauge at time of last tick
// dole - Current dole for item X
// carry - carry for item X slave Y
// prev_req - Numreq for previous request for item X slave Y
// * doleinfo_last -
// Object with 2 values for last "tick period": numreq - total number or
// requests for item, numslave - number of slaves requesting that item
// debt - debt for item X slave Y
//Returns: Array of 3 elements
//0.Number of items to give in that dose
//1.New dole for item X
//2.New carry for item X slave Y
//3.New debt
function Dose(numreq,instore,store_last_tick,dole,carry,prev_req,numreq_total_adj,debt)
{
numreq=Number(numreq);
instore=Number(instore);
var instore_adj=instore;
if (instore_adj==0) instore_adj=0.1;
// Returns: Array of 3 elements
// 0.Number of items to give in that dose
// 1.New dole for item X
// 2.New carry for item X slave Y
// 3.New debt
function dose(numreq, instore, store_last_tick, dole, carry, prev_req, numreq_total_adj, debt) {
numreq = Number(numreq);
instore = Number(instore);
let 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
var inputs=inp1.concat(inp2);
var internal=new Array(20).fill(0);
var outputs=[0,0,0,0];
//NN: Please don't touch :), code is generated
let b = Math.trunc(prev_req / instore_adj * numreq);
let inp1 = [numreq, instore_adj, store_last_tick / numreq, dole, carry]; // 0..4
let 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
let inputs = inp1.concat(inp2);
let internal = new Array(20).fill(0);
let 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;
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);
let res1 = Number(outputs[0]) * numreq+outputs[3] + debt;
let 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;
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;
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];
carry = sigmoid0(outputs[2] * carry + outputs[0]) || 0;
return [res_capped, dole + outputs[1], carry, debt];
}
//TICK - Updates values for item X, should be called once for each item for each round of all slaves requesting that item
//Instore - number of items for item X in master gauge (currently)
//dole - Current dole for item X
//store_last_tick - Number of items for item X in master gauge at time of last tick (not this one)
//* doleinfo_last - Object with 2 values for last "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item
//* doleinfo_new - Object with 2 values for new "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item
// TICK -
// Updates values for item X, should be called once for each item for
// each round of all slaves requesting that item Instore - number of
// items for item X in master gauge (currently)
// dole - Current dole for item X
// store_last_tick -
// Number of items for item X in master gauge at time of last tick (not this one)
// * doleinfo_last -
// Object with 2 values for last "tick period": numreq - total number or
// requests for item, numslave - number of slaves requesting that item
//
// * doleinfo_new -
// Object with 2 values for new "tick period": numreq - total number or
// requests for item, numslave - number of slaves requesting that item
//Returns: Array of 2 elements
//0.New dole for item X
//1.Guessed percentage average of items supplied in last 10 ticks
function Tick(instore,dole,store_last_tick,numreq_total_adj)
{
var instore_adj=instore;
if (instore_adj==0) instore_adj=0.1;
var inputs=[instore,dole,store_last_tick/instore_adj,store_last_tick/numreq_total_adj,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
// Returns: Array of 2 elements
// 0.New dole for item X
// 1.Guessed percentage average of items supplied in last 10 ticks
function tick(instore, dole, store_last_tick, numreq_total_adj) {
let instore_adj = instore;
if (instore_adj === 0) {
instore_adj = 0.1;
}
let inputs = [
instore,
dole,
store_last_tick / instore_adj,
store_last_tick / numreq_total_adj,
store_last_tick,
numreq_total_adj,
];
let internal = new Array(10).fill(0);
let outputs = [0, 0];
// NN: Please don't touch :), code is generated
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]);
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;
avg = outputs[1];
if (numreq_total_adj < 0.11) {
// When no item's are requested, result is 0.1; In that case there is
// no deficit, so return 1 = 100% demand fulfilled
avg = 1;
}
if (avg > 1) { avg=1; }
if (avg < 0) { avg=0; }
return [sigmoid0(outputs[0]),avg];
return [sigmoid0(outputs[0]), avg];
}
module.exports = { Dose,Tick };
module.exports = { dose, tick };
+8 -8
View File
@@ -1,8 +1,8 @@
"use strict";
const link = require("@clusterio/lib/link");
const config = require("@clusterio/lib/config");
const libLink = require("@clusterio/lib/link");
const libConfig = require("@clusterio/lib/config");
class MasterConfigGroup extends config.PluginConfigGroup {}
class MasterConfigGroup extends libConfig.PluginConfigGroup {}
MasterConfigGroup.groupName = "subspace_storage";
MasterConfigGroup.define({
name: "autosave_interval",
@@ -28,7 +28,7 @@ MasterConfigGroup.define({
});
MasterConfigGroup.finalize();
class InstanceConfigGroup extends config.PluginConfigGroup {}
class InstanceConfigGroup extends libConfig.PluginConfigGroup {}
InstanceConfigGroup.groupName = "subspace_storage";
InstanceConfigGroup.define({
name: "log_item_transfers",
@@ -65,7 +65,7 @@ module.exports = {
messages: {
// XXX this should be a request to be reliable
place: new link.Event({
place: new libLink.Event({
type: "subspace_storage:place",
links: ["instance-slave", "slave-master"],
forwardTo: "master",
@@ -74,7 +74,7 @@ module.exports = {
"items": items,
},
}),
remove: new link.Request({
remove: new libLink.Request({
type: "subspace_storage:remove",
links: ["instance-slave", "slave-master"],
forwardTo: "master",
@@ -86,7 +86,7 @@ module.exports = {
"items": items,
},
}),
getStorage: new link.Request({
getStorage: new libLink.Request({
type: "subspace_storage:get_storage",
links: ["instance-slave", "slave-master"],
forwardTo: "master",
@@ -94,7 +94,7 @@ module.exports = {
"items": items,
},
}),
updateStorage: new link.Event({
updateStorage: new libLink.Event({
type: "subspace_storage:update_storage",
links: ["master-slave", "slave-instance"],
broadcastTo: "instance",
+6 -6
View File
@@ -1,8 +1,8 @@
"use strict";
const fs = require("fs-extra");
const plugin = require("@clusterio/lib/plugin");
const luaTools = require("@clusterio/lib/luaTools");
const libPlugin = require("@clusterio/lib/plugin");
const libLuaTools = require("@clusterio/lib/lua_tools");
function unexpectedError(err) {
console.log("Unexpected error in subspace_storage");
@@ -10,7 +10,7 @@ function unexpectedError(err) {
console.log(err);
}
class InstancePlugin extends plugin.BaseInstancePlugin {
class InstancePlugin extends libPlugin.BaseInstancePlugin {
async init() {
this.instance.server.on("ipc-subspace_storage:output", (output) => {
this.provideItems(output).catch(unexpectedError);
@@ -29,7 +29,7 @@ class InstancePlugin extends plugin.BaseInstancePlugin {
let response = await this.info.messages.getStorage.send(this.instance);
// TODO Diff with dump of invdata produce minimal command to sync
let itemsJson = luaTools.escapeString(JSON.stringify(response.items));
let itemsJson = libLuaTools.escapeString(JSON.stringify(response.items));
await this.instance.server.sendRcon(`/sc __subspace_storage__ UpdateInvData("${itemsJson}", true)`, true);
}
@@ -67,7 +67,7 @@ class InstancePlugin extends plugin.BaseInstancePlugin {
console.log(response.items);
}
let itemsJson = luaTools.escapeString(JSON.stringify(response.items));
let itemsJson = libLuaTools.escapeString(JSON.stringify(response.items));
await this.instance.server.sendRcon(`/sc __subspace_storage__ Import("${itemsJson}")`, true);
}
@@ -86,7 +86,7 @@ class InstancePlugin extends plugin.BaseInstancePlugin {
// XXX this should be moved to instance/clusterio api
items.push(["signal-unixtime", Math.floor(Date.now()/1000)]);
let itemsJson = luaTools.escapeString(JSON.stringify(items));
let itemsJson = libLuaTools.escapeString(JSON.stringify(items));
await this.instance.server.sendRcon(`/sc __subspace_storage__ UpdateInvData("${itemsJson}")`, true);
}
}
+10 -10
View File
@@ -2,32 +2,32 @@
const fs = require("fs-extra");
const path = require("path");
const database = require("@clusterio/lib/database");
const plugin = require("@clusterio/lib/plugin");
const prometheus = require("@clusterio/lib/prometheus");
const libDatabase = require("@clusterio/lib/database");
const libPlugin = require("@clusterio/lib/plugin");
const { Counter, Gauge } = require("@clusterio/lib/prometheus");
const routes = require("./routes");
const dole = require("./dole");
const exportCounter = new prometheus.Counter(
const exportCounter = new Counter(
"clusterio_subspace_storage_export_total",
"Resources exported by instance",
{ labels: ["instance_id", "resource"] }
);
const importCounter = new prometheus.Counter(
const importCounter = new Counter(
"clusterio_subspace_storage_import_total",
"Resources imported by instance",
{ labels: ["instance_id", "resource"] }
);
const masterInventoryGauge = new prometheus.Gauge(
const masterInventoryGauge = new Gauge(
"clusterio_subspace_storage_master_inventory",
"Amount of resources stored on master",
{ labels: ["resource"] }
);
class MasterPlugin extends plugin.BaseMasterPlugin {
class MasterPlugin extends libPlugin.BaseMasterPlugin {
async init() {
this.items = await loadDatabase(this.master.config);
@@ -40,7 +40,7 @@ class MasterPlugin extends plugin.BaseMasterPlugin {
});
}, this.master.config.get("subspace_storage.autosave_interval") * 1000);
this.neuralDole = new dole.neuralDole({ items: this.items });
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();
@@ -175,12 +175,12 @@ async function loadDatabase(config) {
console.log(`Loading ${itemsPath}`);
try {
let content = await fs.readFile(itemsPath);
return new database.ItemDatabase(JSON.parse(content));
return new libDatabase.ItemDatabase(JSON.parse(content));
} catch (err) {
if (err.code === "ENOENT") {
console.log("Creating new item database");
return new database.ItemDatabase();
return new libDatabase.ItemDatabase();
} else {
throw err;
-2
View File
@@ -1,8 +1,6 @@
"use strict";
const path = require("path");
const prometheus = require("@clusterio/lib/prometheus");
function addApiRoutes(app, items, endpointHitCounter) {
+3 -1
View File
@@ -17,7 +17,9 @@ describe("subspace_storage endpoints", function() {
let contains20SteelPlate = false;
inventory.forEach(itemStack => {
if(itemStack.name == "steel-plate" && itemStack.count >= 20) contains20SteelPlate = true;
if (itemStack.name === "steel-plate" && itemStack.count >= 20) {
contains20SteelPlate = true;
}
});
assert(contains20SteelPlate, "Please ensure there are at least 20 steel plate in the inventory");
});
-18
View File
@@ -1,18 +0,0 @@
/*
This clusterio plugin is tasked to read your inventory and logistic request slots
it will then attempt to fulfill those requests using items provided by the master
server. There *should* be an in game GUI to toggle this.
*/
module.exports = {
// Name of package. For display somewhere I guess.
name: "inventoryImports",
version: "1.0.0",
arguments: ["index.js"],
description: "Clusterio plugin to fill character logistic slots with master imports",
// We'll send everything in this file to your stdin. Beware.
scriptOutputFileSubscription: "inventoryImports.txt",
fileReadDelay: 100,
}
-27
View File
@@ -1,27 +0,0 @@
const needle = require("needle");
const isJson = require("./isJson");
const pluginConfig = require("./config");
const functions = require("./interfacingFunctions");
// poll inventories
class inventoryImports {
constructor(slaveConfig, messageInterface){
this.config = slaveConfig;
this.messageInterface = messageInterface;
messageInterface("/silent-command game.print('InventoryImports enabled')");
setInterval(function(){
// messageInterface("Polling inventories\n")
messageInterface(functions.pollInventories(pluginConfig.scriptOutputFileSubscription));
}, 10000);
}
scriptOutput(data){
if(data !== null && isJson(data)){
let inventory = JSON.parse(data);
// process inventory of players and ask master for more stuff
functions.handleInventory(inventory, this.config, this.messageInterface);
}
}
}
module.exports = inventoryImports;
@@ -1,149 +0,0 @@
const objectOps = require("@clusterio/lib/objectOps");
const needle = require("needle");
const pluginConfig = require("./config")
const isJson = require("./isJson")
function handleInventory(json, config, callback){
/*
json is one of these two samples
{"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}}
{"exports":{"1":[{"name":"iron-ore","count":0},{"name":"steel-plate","count":0}]}}
we want to seperate players, parse their json and compare to request slots
then ask master for the difference
we must then prepare it all and inject it into players inventories with lua before
the next json report arrives to avoid duplicating orders
*/
if(json && typeof json != "string" && typeof json == "object"){
let exports = {};
// this is an inventory report, check whats missing and request from master
// {"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}}
if(json.players){
let playerNames = Object.keys(json.players);
for(let i = 0; i < playerNames.length; i++) {
// haha, this turned into bit of a mess didn't it?
let playerInv = json.players[playerNames[i]];
// create a copy of player.requestSlots
let toRequest = objectOps.deepclone(playerInv.requestSlots);
let inventory = playerInv.inventory;
let itemsToConfirm = Object.keys(toRequest).length;
// create a place to store the object we have confirmed for importing
let confirmedItems = {};
let collectStacks = function(stack){
itemsToConfirm--;
if(stack){
// because of the way we collect data in a keyed JSON object, this first part of the IF cannot happen.
/* istanbul ignore next */
if(confirmedItems[stack.name] && confirmedItems[stack.name] > 0){
confirmedItems[stack.name] += stack.count;
} else {
confirmedItems[stack.name] = stack.count;
}
}
if(itemsToConfirm == 0 && Object.keys(confirmedItems).length > 0){
// return LUA
callback(insertItemsFromObject(confirmedItems, playerNames[i]));
}
}
// subtract items already in inventory
Object.keys(playerInv.requestSlots).forEach(function(key){
// if this item in inventory is also in a request slot, subtract invCount from requestCount
if(inventory[key]){
toRequest[key] -= inventory[key];
}
// if the difference is more than 0, request item from master
if(toRequest[key] > 0){
let request = {
name:key,
count:toRequest[key],
}
//console.log("Requesting: "+JSON.stringify(request));
// request the difference from master
function callback(err, response, body) {
if (!err && response && response.body && (typeof response.body == "object" || isJson(response.body))) {
// confirmed orders, already removed from master inventory
//response.body.name: response.body.count
if(typeof response.body == "string") response.body = JSON.parse(response.body);
let stack = {name:response.body.name,count:Number(response.body.count)}
collectStacks(stack);
} else {
collectStacks(false);
}
}
needle.post(
config.masterURL + '/api/remove',
request,
{json:true,headers: {'x-access-token': config.masterAuthToken}},
callback
);
} else {
collectStacks(false);
}
});
}
}
// {"exports":{"1":[{"name":"iron-ore","count":0},{"name":"steel-plate","count":0}]}}
// this is a report of stuff we didn't have space for, send back to master
if(json.exports){
// loop over players (normally only one, but whatever)
Object.keys(json.exports).forEach(function(playerName){
// loop over simpleItemStacks to export
let stackArray = json.exports[playerName];
for(let i = 0; i < stackArray.length; i++){
if(stackArray[i].count > 0){
// console.log("Returning overflow: " + JSON.stringify(stackArray[i]));
needle.post(
config.masterURL + '/api/place',
stackArray[i],
{headers: {'x-access-token': config.masterAuthToken}},
function (err, resp, body) {
// console.log(body);
}
);
}
}
});
}
} else return false; // inventory is falsey or a string
}
function insertItemsFromObject(confirmedItems, playerName){
// expeced input
// {["iron-plate"]:100, coal:20}
// make lua table
// we gotta construct a string because I can't find any nice library for this
if(playerName && confirmedItems){
if(typeof playerName != "string") {
throw new Error("playerName is: '"+typeof playerName+"' instead of string!");
}
let itemTable = "{";
Object.keys(confirmedItems).forEach(function(name){
let count = confirmedItems[name];
if(count > 0){
itemTable += '["'+name+'"]='+Math.floor(count)+',';
}
});
itemTable += "}";
let outputFile = pluginConfig.scriptOutputFileSubscription;
// a="'+players[i]+'"
// b='+itemTable+'
return '/silent-command local a="'+playerName+'"local b='+itemTable+'local c={}if game.players[a]and game.players[a].connected then local d='+"'"+'{"exports":{"'+"'"+'..a..'+"'"+'":['+"'"+'local e=false;for f,g in pairs(b)do local h=game.players[a].insert{name=f,count=g}if e then d=d..'+"'"+','+"'"+'else e=true end;d=d..'+"'"+'{"name":"'+"'"+'..f..'+"'"+'","count":'+"'"+'..g-h..'+"'"+'}'+"'"+'end;d=d..'+"'"+']}}'+"'"+'game.write_file("'+outputFile+'",d,true,0)end';
}
}
function pollInventories(outputFile){
// console.log("/silent-command "+'local a=game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()local b=game.players["Danielv123"].get_quickbar().get_contents()local c={}local d="{inventory:{"for e,f in pairs(a)do d=d.."['+"'"+'"..e.."'+"'"+']:"..f..","end;for e,f in pairs(b)do d=d.."['+"'"+'"..e.."'+"'"+']:"..f..","end;d=d.."},requestSlots:{"for e,f in pairs(c)do d=d.."['+"'"+'"..e.."'+"'"+']:"..f..","end;d=d.."}}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",d)');
// console.log("Running inventory LUA")
//console.log("/silent-command "+'local a=game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()local b=game.players["Danielv123"].get_quickbar().get_contents()local c={}for d=1,game.players["Danielv123"].force.character_logistic_slot_count do c[d]=game.players["Danielv123"].character.get_request_slot(d)end;local e="{inventory:{"for f,g in pairs(a)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;for f,g in pairs(b)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;e=e.."},requestSlots:{"for f,g in pairs(c)do e=e.."['+"'"+'"..g["name"].."'+"'"+']:"..g["count"]..", "end;e=e.."}}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",e)')
// console.log("/silent-command "+'local a="{"for b,c in pairs(game.players)do local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end;a=a.."}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",a)')
//return "/silent-command "+'local a="{"for b,c in pairs(game.players)do if c.connected then local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end end;a=a.."}"game.write_file("'+outputFile+'",a)'
//return '/silent-command '+'local a='+"'"+'{"players":{'+"'"+'local b=false;for c,d in pairs(game.players)do if d.connected and d.character then if b then a=a..'+"'"+','+"'"+'else b=true end;local e=game.players[c].get_inventory(defines.inventory.player_main).get_contents()local f=game.players[c].get_quickbar().get_contents()local g={}local z={}for j,k in pairs(e)do if z[j] ~= nil then z[j] = z[j] + k else z[j] = k end end for j,k in pairs(f)do if z[j] ~= nil then z[j] = z[j] + k else z[j] = k end end for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'+"'"+'"'+"'"+'..c..'+"'"+'":{"inventory":{'+"'"+'local i=false;for j,k in pairs(z)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;a=a..'+"'"+'},"requestSlots":{'+"'"+'i=false;for j,k in pairs(g)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..k['+"'"+'name'+"'"+']..'+"'"+'":'+"'"+'..k['+"'"+'count'+"'"+']end;a=a..'+"'"+'}}'+"'"+'end end;a=a.."}}"game.write_file("'+outputFile+'",a,true,0)'
// Warning: This command is not in sync with lua commands.lua
return `/silent-command local a='{"players":{'local b=false;for c,d in pairs(game.players)do if d.connected and d.character then if b then a=a..','else b=true end;local e=game.players[c].get_inventory(defines.inventory.character_main).get_contents()local g={}for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'"'..c..'":{"inventory":{'local i=false;for j,k in pairs(e)do if i then a=a..','else i=true end;a=a..'"'..j..'":'..k end;a=a..'},"requestSlots":{'i=false;for j,k in pairs(g)do if i then a=a..','else i=true end;a=a..'"'..k['name']..'":'..k['count']end;a=a..'}}'end end;a=a.."}}"game.write_file("${outputFile}",a,true,0)`
}
module.exports = {
handleInventory: handleInventory,
pollInventories: pollInventories,
insertItemsFromObject: insertItemsFromObject,
}
-11
View File
@@ -1,11 +0,0 @@
module.exports = function(x){
if(x === false || x === true || x === null) return true; // === false is valid JSON, so is true and null
var isNotJson = false
try{
JSON.parse(x);
} catch (e) {
isNotJson = true;
}
return !isNotJson;
}
@@ -1,181 +0,0 @@
-- WARNING: The commands in this file is not in sync with interfacingFunctions.js
-- Lua cheatsheet
/c game.write_file("test.txt",serpent.block(game.players["Danielv123"].get_quickbar().get_contents()))
/c game.write_file("test.txt",serpent.block(game.players["Danielv123"].get_inventory(defines.inventory.character_main).get_contents()))
-- print JSON of entire inventory and request slots to file
-- TODO: Get requester slot data as well
-- requester slot data is game.players.character.get_request_slot(slot)
-- returns {name="copper-plate", count=47} or nil if nothing is set
-- maybe game.players.character.item_requests? http://lua-api.factorio.com/latest/LuaEntity.html#LuaEntity.item_requests
/c
local requests = {}
for i = 1, game.player.force.character_logistic_slot_count do
requests[i] = game.players["Danielv123"].character.get_request_slot(i)
end
local o = "Requests: "
for k,v in pairs(requests) do
o = o..v["name"]..": "..v["count"]..", "
end
game.print(o)
-- sum up our inventory and request slots and print it as valid JSON
-- turns out the valid part is important
/c
local o = '{"players":{'
local notFirst = false
for a,b in pairs(game.players) do
if b.connected and b.character then
if notFirst then
o = o .. ','
else
notFirst = true
end
local inventory = game.players[a].get_inventory(defines.inventory.character_main).get_contents()
local quickbar = game.players[a].get_quickbar().get_contents()
--[[ Get contents of requester slots into a table ]]
local requests = {}
for i = 1, game.players[a].force.character_logistic_slot_count do
requests[i] = game.players[a].character.get_request_slot(i)
end
o = o..'"'..a..'":{"inventory":{'
--[[ generate JSON table ]]
local addComma = false
for k,v in pairs(inventory) do
if addComma then
o = o..','
else
addComma = true
end
o = o..'"'..k..'":'..v
end
for k,v in pairs(quickbar) do
if addComma then
o = o..','
else
addComma = true
end
o = o..'"'..k..'":'..v
end
o = o..'},"requestSlots":{'
addComma = false
for k,v in pairs(requests) do
if addComma then
o = o..','
else
addComma = true
end
o = o..'"'..v['name']..'":'..v['count']
end
o = o..'}}' --[[ We need a comma after these two if there is more than 1 player, handled up top by notFirst]]
end
end
o = o.."}}"
game.write_file("t.txt", o, true, 0)
game.print(o)
--[[
Example output
{"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}}
Minification procedure:
Run through https://mothereff.in/lua-minifier
Replace ' with '+"'"+'
Prepend /silent-command
remove game.print(o)
replace "t.txt" with "'+outputFile+'"
enclose in ''
]]
-- minified strings (newest on the top)
local a='+"'"+'{"players":{'+"'"+'local b=false;for c,d in pairs(game.players)do if d.connected then if b then a=a..'+"'"+','+"'"+'else b=true end;local e=game.players[c].get_inventory(defines.inventory.character_main).get_contents()local f=game.players[c].get_quickbar().get_contents()local g={}for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'+"'"+'"'+"'"+'..c..'+"'"+'":{"inventory":{'+"'"+'local i=false;for j,k in pairs(e)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;for j,k in pairs(f)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;a=a..'+"'"+'},"requestSlots":{'+"'"+'i=false;for j,k in pairs(g)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..k['+"'"+'name'+"'"+']..'+"'"+'":'+"'"+'..k['+"'"+'count'+"'"+']end;a=a..'+"'"+'}}'+"'"+'end end;a=a.."}}"game.write_file("'+outputFile+'",a,true,0)
-- offline playes
local a="{"for b,c in pairs(game.players)do if c.connected then local d=game.players[b].get_inventory(defines.inventory.character_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end end;a=a.."}"game.print(a)
-- multiple players
local a="{"for b,c in pairs(game.players)do local d=game.players[b].get_inventory(defines.inventory.character_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end;a=a.."}"game.print(a)
local a=game.players["Danielv123"].get_inventory(defines.inventory.character_main).get_contents()local b=game.players["Danielv123"].get_quickbar().get_contents()local c={}for d=1,game.players["Danielv123"].force.character_logistic_slot_count do c[d]=game.players["Danielv123"].character.get_request_slot(d)end;local e="{inventory:{"for f,g in pairs(a)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;for f,g in pairs(b)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;e=e.."},requestSlots:{"for f,g in pairs(c)do e=e.."['+"'"+'"..g["name"].."'+"'"+']:"..g["count"]..", "end;e=e.."}}"game.print(e)
local inventory = game.players["Danielv123"].get_inventory(defines.inventory.character_main).get_contents()local quickbar = game.players["Danielv123"].get_quickbar().get_contents()local requests = {}for i = 1, game.player.force.character_logistic_slot_count do requests[i] = game.players["Danielv123"].character.get_request_slot(i) end local o = "{inventory:{" for k,v in pairs(inventory) do o = o.."['+"'"+'"..k.."'+"'"+']:"..v.."," end for k,v in pairs(quickbar) do o = o.."['+"'"+'"..k.."'+"'"+']:"..v.."," end o = o.."},requestSlots:{" for k,v in pairs(requests) do o = o.."['+"'"+'"..v["name"].."'+"'"+']:"..v["count"]..", " end o = o.."}}" game.write_file("test.txt", o,true,0)
-- insert items in players inventory and export leftovers
-- name should be players[i]
/c
local name = "1"
local items = {["iron-ore"]=30, ["steel-plate"]=120}
local items_failed = {}
if(game.players[name] and game.players[name].connected) then
for item,count in pairs(items) do
local i = game.players[name].insert{name=item,count=count}
if not items_failed[name] then
items_failed[name] = {}
end
if items_failed[name][item] then
items_failed[name][item] = items_failed[name][item] + count - i
else
items_failed[name][item] = count - i
end
end
game.write_file("t.txt", serpent.line({["exports"]=items_failed}, {["comment"]=false, ["compact"]=true}),true,0)
end
--[[
Writes something like
{exports={["1"]={["iron-ore"]=0,["steel-plate"]=0}}}
converting to JS would be
replace "[" and "]" with ""???
replace "=" with ":"
]]
--[[
JSON edition
{"exports":{"1":[{"name":"iron-ore","count":0},{"name":"steel-plate","count":0}]}}
Should always return valid JSON
]]
/c
local name = "1"
local items = {["iron-ore"]=30, ["steel-plate"]=120}
if(game.players[name] and game.players[name].connected) then
local o = '{"exports":{"'..name..'":['
local addComma = false
for item,count in pairs(items) do
local i = game.players[name].insert{name=item,count=count}
if addComma then
o = o..','
else
addComma = true
end
o = o .. '{"name":"'..item..'","count":'..count - i..'}'
end
o = o .. ']}}'
game.write_file("t.txt", o,true,0)
end
--[[
Minification procedure:
Run through https://mothereff.in/lua-minifier
Replace ' with '+"'"+'
Prepend /silent-command
replace "t.txt" with "'+outputFile+'"
enclose in ''
]]
-- JSON outputFile
local a="1"local b={["iron-ore"]=30,["steel-plate"]=120}local c={}if game.players[a]and game.players[a].connected then local d='+"'"+'{"exports":{"'+"'"+'..a..'+"'"+'":['+"'"+'local e=false;for f,g in pairs(b)do local h=game.players[a].insert{name=f,count=g}if e then d=d..'+"'"+','+"'"+'else e=true end;d=d..'+"'"+'{"name":"'+"'"+'..f..'+"'"+'","count":'+"'"+'..g-h..'+"'"+'}'+"'"+'end;d=d..'+"'"+']}}'+"'"+'game.write_file("'+outputFile+'",d,true,0)end
-- output to serpented LUA table
local a="Danielv123"local b={["iron-ore"]=30,["steel-plate"]=120}local c={}if game.players[a]and game.players[a].connected then for d,e in pairs(b)do local f=game.players[a].insert{name=d,count=e}if not c[a]then c[a]={}end;if c[a][d]then c[a][d]=c[a][d]+e-f else c[a][d]=e-f end end;game.write_file("'+outputFile+'",serpent.line(c,{["comment"]=false,["compact"]=true}),true,0)end
@@ -1,159 +0,0 @@
const assert = require("assert");
const child_process = require("child_process");
const functions = require("../interfacingFunctions");
const luamin = require("luamin");
const isFactorioCommand = require("@clusterio/lib/isFactorioCommand");
const nock = require("nock");
// sinon does weird prototype manipulation and so doen't need to be saved to anything.
require("mocha-sinon");
const sinon = require("sinon");
describe("inventoryImports/interfacingFunctions.js", function(){
describe("handleInventory(inventory, config)", function(){
let config = {
masterURL:"http://localhost:8080",
masterAuthToken:"",
}
it("returns false if given invalid input", function(){
assert(functions.handleInventory("this is a string") === false, "when given strings it should return false and do nothing");
});
it("Sends post requests and outputs factorio commands", function(done){
let inventory = {
"players":{
"Danielv123":{
"inventory":{
"stone":12,
"iron-ore":120,
"raw-wood":10,
"iron-plate":8,
"steel-plate":480
},
"requestSlots":{
"iron-plate":100,
"steel-plate":480,
"raw-wood":20,
"bullshit":0,
}
}
}
}
var scope = nock('http://localhost:8080')
.post('/api/remove', function(body){
// check that we are requesting the right things, () to convert to boolean
// 92 because 100 requested and 8 already in inventory
if(body.name == "iron-plate") assert(body.count == "92");
// nock wants us to return true if this is the correct body and we should reply
// if we return false we won't send a reply.
return true;
})
// nock destroys the endpoints after being hit once, this changes the number of hits required for destruction
.times(10)
// respond with JSON object instead of url query parameters
.reply(200,function(uri, req){
return req
},{
'Content-Type': 'application/json',
'Accept': 'application/json'
})
// nock logging statement .log((data) => console.log(data));
functions.handleInventory(inventory, config, callback);
function callback(x){
assert(x.includes(92) && x.includes("iron-plate"), "it should have included the items it was going to import");
assert(!x.includes("steel-plate"), "Inventory is already filled with steel so shouldn't be importing that");
assert(isFactorioCommand(x), "it should have returned a valid factorio command");
assert(x.includes('"Danielv123"'), "it should include the name of the player that will recieve the items");
done();
}
});
it("posts leftovers to master", function(done){
let inventory2 = {
"exports":{
"1":[
{"name":"iron-ore","count":1200},
{"name":"steel-plate","count":1337},
{"name":"copper-plate","count":0},
]
}
}
let timesRan = 0;
let callback = function(){
timesRan++
assert(timesRan <= 2, "placed more than 2 items, unexpeceted test result");
if(timesRan == 2){
done();
}
}
var scope = nock('http://localhost:8080')
.post('/api/place', function(body){
// check that we are requesting the right things
assert(body.name != "copper-plate", "copper plate should not be placed because it is 0");
assert(body.count > 0, "no need for placing 0 things");
assert(Number(body.count) != NaN, "count should be a number");
callback();
return true;
})
// nock destroys the endpoints after being hit once, this changes the number of hits required for destruction
.times(10)
.reply(200,"affirmative")
// if it tries using the nonexisting callback (argv 3) it will throw. It shoudln't.
assert.doesNotThrow(function(){
functions.handleInventory(inventory2, config);
}, "this here threw for some reason, remove assert.doesNotThrow for easier debugging of this issue");
});
});
describe("pollInventories(outputfile)", function(){
it("Returns a big factorio command as string", function(){
let x = functions.pollInventories("output.txt");
assert(typeof x == "string", "pollInventories() should always return a string");
assert(isFactorioCommand(x), "This is not a command factorio will accept, please prepend /c");
assert(x.length > 20, "LUA string seems to short, this ain't good");
});
it("Returns valid LUA", function(){
let x = functions.pollInventories("output.txt");
// remove string added to make factorio interpret as command
x = x.replace("/silent-command ","").replace("/c ","");
let y = false;
// luamin throws when it recieves something that is invalid LUA
assert.doesNotThrow(function(){
y = luamin.minify(x);
}, "Invalid LUA supplied");
assert(typeof y == "string", "luamin does not return string???");
assert(y.length > 20, "Minified LUA too short, something is up");
});
});
describe("insertItemsFromObject(object)", function(){
let testObject = {"copper-plate":100, "iron-ore":1337}
let playerName = "Danielv123"
it("Returns undefined if any of the parameters are missing", function(){
let x = functions.insertItemsFromObject();
assert(x === undefined);
let y = functions.insertItemsFromObject(testObject);
assert(y === undefined);
let z = functions.insertItemsFromObject(undefined, playerName);
assert(z === undefined);
});
it("Throws if player name is not a string", function(){
assert.throws(function(){
let x = functions.insertItemsFromObject(testObject, testObject);
}, Error("playerName is: 'object' instead of string!"));
});
it("Returns a big factorio command as string", function(){
let x = functions.insertItemsFromObject(testObject, playerName);
assert(typeof x == "string", "LUA returned should be a string");
assert(x.length > 20, "LUA strings are quite long");
assert(isFactorioCommand(x), "This is not a command factorio will accept, please prepend /c");
});
it("The returned string is a valid factorio command", function(){
let x = functions.insertItemsFromObject(testObject, playerName);
assert(isFactorioCommand(x) == true);
});
});
});
@@ -1,20 +0,0 @@
const isJson = require("../isJson");
const assert = require("assert");
describe("inventoryImports/isJson.js()", function(){
it("checks if a thing is valid JSON", function(){
assert(isJson("string") === false);
assert(isJson("{js_object_notation='very false'}") === false);
assert(isJson('{ "name":"John", "age":31, "city":"New York" }') === true, "This is valid JSON, lib disagrees");
});
it("recognizes that true and false are both valid JSON", function(){
assert(isJson(true) === true);
assert(isJson(false) === true);
});
it("Handles null as valid JSON (after spec)", function(){
assert(isJson(null) === true);
});
it("Recognizes that JSON doesn't allow trailing commas (,)", function(){
assert(isJson('["a", "b", ]') === false, "JSON should not allow trailing commas");
});
});
@@ -1,4 +0,0 @@
// These are libraries that does not have any units test, they are
// required here in order to track coverage for them.
require('../index');
+25 -22
View File
@@ -3,13 +3,13 @@ const assert = require("assert").strict;
const fs = require("fs-extra");
const path = require("path");
const link = require("@clusterio/lib/link");
const libLink = require("@clusterio/lib/link");
const { slowTest, get, execCtl, sendRcon, getControl, instancesDir } = require("./index");
async function getInstances() {
let result = await link.messages.listInstances.send(getControl());
let result = await libLink.messages.listInstances.send(getControl());
return new Map(result.list.map(instance => [instance.id, instance]));
}
@@ -170,14 +170,14 @@ describe("Integration of Clusterio", function() {
describe("user set-admin/whitelisted/banned", function() {
async function getUsers() {
let result = await link.messages.listUsers.send(getControl());
let result = await libLink.messages.listUsers.send(getControl());
return new Map(result.list.map(user => [user.name, user]));
}
let lists = ["admin", "whitelisted", "banned"];
it("should add and remove the given user to the list", async function() {
slowTest(this);
await link.messages.createUser.send(getControl(), { name: "list_test" });
await libLink.messages.createUser.send(getControl(), { name: "list_test" });
let user = (await getUsers()).get("list_test");
for (let list of lists) {
assert.equal(user[`is_${list}`], false, `unexpected ${list} status`);
@@ -248,9 +248,12 @@ describe("Integration of Clusterio", function() {
it("should create the given role", async function() {
let args = "--description \"A temp role\" --permissions core.control.connect";
await execCtl(`role create temp ${args}`);
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"] });
let result = await libLink.messages.listRoles.send(getControl());
let tempRole = result.list.find(role => role.name === "temp");
assert.deepEqual(
tempRole,
{ id: 5, name: "temp", description: "A temp role", permissions: ["core.control.connect"] }
);
});
});
@@ -258,18 +261,18 @@ describe("Integration of Clusterio", function() {
it("should modify the given role", async function() {
let args = "--name new --description \"A new role\" --permissions";
await execCtl(`role edit temp ${args}`);
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: [] });
let result = await libLink.messages.listRoles.send(getControl());
let newRole = result.list.find(role => role.name === "new");
assert.deepEqual(newRole, { id: 5, name: "new", description: "A new role", permissions: [] });
});
});
describe("role delete", function() {
it("should modify the given role", async function() {
await execCtl("role delete new");
let result = await link.messages.listRoles.send(getControl());
let role = result.list.find(role => role.name === "new");
assert(!role, "Role was not deleted");
let result = await libLink.messages.listRoles.send(getControl());
let newRole = result.list.find(role => role.name === "new");
assert(!newRole, "Role was not deleted");
});
});
@@ -282,27 +285,27 @@ describe("Integration of Clusterio", function() {
describe("user create", function() {
it("should create the given user", async function() {
await execCtl("user create temp");
let result = await link.messages.listUsers.send(getControl());
let user = result.list.find(user => user.name === "temp");
assert(user, "user was not created");
let result = await libLink.messages.listUsers.send(getControl());
let tempUser = result.list.find(user => user.name === "temp");
assert(tempUser, "user was not created");
});
});
describe("user set-roles", function() {
it("should set the roles on the user", async function() {
await execCtl("user set-roles temp Admin");
let result = await link.messages.listUsers.send(getControl());
let user = result.list.find(user => user.name === "temp");
assert.deepEqual(user.roles, [0]);
let result = await libLink.messages.listUsers.send(getControl());
let tempUser = result.list.find(user => user.name === "temp");
assert.deepEqual(tempUser.roles, [0]);
});
});
describe("user delete", function() {
it("should delete the user", async function() {
await execCtl("user delete temp");
let result = await link.messages.listUsers.send(getControl());
let user = result.list.find(user => user.name === "temp");
assert(!user, "user was note deleted");
let result = await libLink.messages.listUsers.send(getControl());
let tempUser = result.list.find(user => user.name === "temp");
assert(!tempUser, "user was note deleted");
});
});
});
+3 -3
View File
@@ -2,17 +2,17 @@
const assert = require("assert").strict;
const path = require("path");
const factorio = require("@clusterio/lib/factorio");
const libFactorio = require("@clusterio/lib/factorio");
describe("Integration of lib/factorio/export", function() {
describe("exportLocale()", function() {
it("returns a nested Map with base game locale information", async function() {
let writePath = path.join("temp", "test", "server");
let testServer = new factorio.FactorioServer("factorio", writePath, {});
let testServer = new libFactorio.FactorioServer("factorio", writePath, {});
await testServer.init();
let locale = await factorio._exportLocale(testServer, new Map(), ["base"], "en");
let locale = await libFactorio._exportLocale(testServer, new Map(), ["base"], "en");
assert(locale instanceof Map, "locale is not a map");
assert.equal(locale.get("entity-name.fish"), "Fish");
+8 -8
View File
@@ -7,14 +7,14 @@ const phin = require("phin");
const util = require("util");
const events = require("events");
const link = require("@clusterio/lib/link");
const libLink = require("@clusterio/lib/link");
const server = require("@clusterio/lib/factorio/server");
class TestControl extends link.Link {
class TestControl extends libLink.Link {
constructor(connector) {
super("control", "master", connector);
link.attachAllMessages(this);
libLink.attachAllMessages(this);
}
async prepareDisconnectRequestHandler(message, request) {
@@ -27,7 +27,7 @@ class TestControl extends link.Link {
async instanceOutputEventHandler() { }
}
class TestControlConnector extends link.WebSocketClientConnector {
class TestControlConnector extends libLink.WebSocketClientConnector {
register() {
this.sendHandshake("register_control", { token: this.token, agent: "clusterioctl", version: "test" });
}
@@ -44,13 +44,13 @@ function slowTest(test) {
test.timeout(20000);
}
async function get(path) {
async function get(urlPath) {
let res = await phin({
method: "GET",
url: `https://localhost:4443${path}`,
url: `https://localhost:4443${urlPath}`,
core: { rejectUnauthorized: false },
});
if (res.statusCode != 200) {
if (res.statusCode !== 200) {
throw new Error(`Got response code ${res.statusCode}, content: ${res.body}`);
}
return res;
@@ -81,7 +81,7 @@ async function execCtl(...args) {
}
async function sendRcon(instanceId, command) {
let response = await link.messages.sendRcon.send(control, { instance_id: instanceId, command });
let response = await libLink.messages.sendRcon.send(control, { instance_id: instanceId, command });
return response.result;
}
+6 -6
View File
@@ -1,10 +1,10 @@
"use strict";
const assert = require("assert").strict;
const fs = require("fs-extra");
const jszip = require("jszip");
const JSZip = require("jszip");
const path = require("path");
const factorio = require("@clusterio/lib/factorio");
const libFactorio = require("@clusterio/lib/factorio");
const { slowTest } = require("./index");
@@ -17,7 +17,7 @@ describe("Integration of lib/factorio/patch", function() {
let savePath = path.join("temp", "test", "integration", "saves", "test.zip");
it("should patch a freeplay game", async function() {
slowTest(this);
await factorio.patch(savePath, [{
await libFactorio.patch(savePath, [{
name: "test",
version: "1.0.0",
dependencies: {},
@@ -33,14 +33,14 @@ describe("Integration of lib/factorio/patch", function() {
require: [],
}]);
let zip = await jszip.loadAsync(await fs.readFile(savePath));
let zip = await JSZip.loadAsync(await fs.readFile(savePath));
assert.equal(await zip.file("test/modules/test/test.lua").async("string"), "-- test\n");
assert.equal(await zip.file("test/modules/subdir/dir/test.lua").async("string"), "-- test\n");
});
it("should remove old modules in a save", async function() {
slowTest(this);
await factorio.patch(savePath, []);
let zip = await jszip.loadAsync(await fs.readFile(savePath));
await libFactorio.patch(savePath, []);
let zip = await JSZip.loadAsync(await fs.readFile(savePath));
assert.equal(zip.file("test/modules/test/test.lua"), null);
});
});
+9 -7
View File
@@ -4,8 +4,8 @@ const events = require("events");
const fs = require("fs-extra");
const path = require("path");
const factorio = require("@clusterio/lib/factorio");
const errors = require("@clusterio/lib/errors");
const libFactorio = require("@clusterio/lib/factorio");
const libErrors = require("@clusterio/lib/errors");
const { slowTest } = require("./index");
@@ -13,7 +13,7 @@ const { slowTest } = require("./index");
describe("Integration of lib/factorio/server", function() {
describe("_getVersion()", function() {
it("should get a version from factorio's changelog.txt", async function() {
let version = await factorio._getVersion(path.join("factorio", "data", "changelog.txt"));
let version = await libFactorio._getVersion(path.join("factorio", "data", "changelog.txt"));
if (!/^\d+\.\d+\.\d+$/.test(version)) {
assert.fail(`Detected version '${version}' does not followed the format x.y.z`);
}
@@ -22,7 +22,7 @@ describe("Integration of lib/factorio/server", function() {
describe("class FactorioServer", function() {
let writePath = path.join("temp", "test", "integration");
let server = new factorio.FactorioServer("factorio", writePath, {});
let server = new libFactorio.FactorioServer("factorio", writePath, {});
let logFile;
before(async function() {
@@ -204,7 +204,7 @@ describe("Integration of lib/factorio/server", function() {
describe(".start() termination detection", function() {
it("should tell if Factorio got killed", async function() {
// This does not work on Windows
if (process.platform == "win32") {
if (process.platform === "win32") {
this.skip();
}
slowTest(this);
@@ -215,7 +215,9 @@ describe("Integration of lib/factorio/server", function() {
await assert.rejects(
startPromise,
new errors.EnvironmentError("Factorio server was unexpectedly killed, is the system low on memory?")
new libErrors.EnvironmentError(
"Factorio server was unexpectedly killed, is the system low on memory?"
)
);
});
@@ -225,7 +227,7 @@ describe("Integration of lib/factorio/server", function() {
await assert.rejects(
server.start("does-not-exist.zip"),
new errors.EnvironmentError("Factorio server unexpectedly shut down with code 1")
new libErrors.EnvironmentError("Factorio server unexpectedly shut down with code 1")
);
});
});
+18 -18
View File
@@ -1,9 +1,9 @@
"use strict";
const assert = require("assert").strict;
const command = require("@clusterio/lib/command");
const errors = require("@clusterio/lib/errors");
const link = require("@clusterio/lib/link");
const libCommand = require("@clusterio/lib/command");
const libErrors = require("@clusterio/lib/errors");
const libLink = require("@clusterio/lib/link");
const mock = require("../mock");
@@ -39,49 +39,49 @@ describe("lib/command", function() {
});
let testControl = new mock.MockControl(mockConnector);
link.messages.listSlaves.attach(testControl);
link.messages.listInstances.attach(testControl);
link.messages.listRoles.attach(testControl);
libLink.messages.listSlaves.attach(testControl);
libLink.messages.listInstances.attach(testControl);
libLink.messages.listRoles.attach(testControl);
describe("resolveSlave", function() {
it("should pass an integer like string back", async function() {
assert.equal(await command.resolveSlave(null, "123"), 123);
assert.equal(await libCommand.resolveSlave(null, "123"), 123);
});
it("should resolve a slave name with the master server", async function() {
assert.equal(await command.resolveSlave(testControl, "Test Slave"), 11);
assert.equal(await libCommand.resolveSlave(testControl, "Test Slave"), 11);
});
it("should throw if slave is not found", async function() {
await assert.rejects(
command.resolveSlave(testControl, "invalid"),
new errors.CommandError("No slave named invalid")
libCommand.resolveSlave(testControl, "invalid"),
new libErrors.CommandError("No slave named invalid")
);
});
});
describe("resolveInstance", function() {
it("should pass an integer like string back", async function() {
assert.equal(await command.resolveInstance(null, "123"), 123);
assert.equal(await libCommand.resolveInstance(null, "123"), 123);
});
it("should resolve an instance name with the master server", async function() {
assert.equal(await command.resolveInstance(testControl, "Test Instance"), 57);
assert.equal(await libCommand.resolveInstance(testControl, "Test Instance"), 57);
});
it("should throw if instance is not found", async function() {
await assert.rejects(
command.resolveInstance(testControl, "invalid"),
new errors.CommandError("No instance named invalid")
libCommand.resolveInstance(testControl, "invalid"),
new libErrors.CommandError("No instance named invalid")
);
});
});
describe("retrieveRole", function() {
it("should retrieve a role by id from an integer like string", async function() {
assert.deepEqual(await command.retrieveRole(testControl, "28"), testRole);
assert.deepEqual(await libCommand.retrieveRole(testControl, "28"), testRole);
});
it("should retrieve a role by name from a string", async function() {
assert.deepEqual(await command.retrieveRole(testControl, "Test Role"), testRole);
assert.deepEqual(await libCommand.retrieveRole(testControl, "Test Role"), testRole);
});
it("should throw if role is not found", async function() {
await assert.rejects(
command.retrieveRole(testControl, "invalid"),
new errors.CommandError("No role named invalid")
libCommand.retrieveRole(testControl, "invalid"),
new libErrors.CommandError("No role named invalid")
);
});
});
+3 -3
View File
@@ -415,14 +415,14 @@ describe("lib/config/classes", function() {
describe(".registerGroup()", function() {
it("should throw if the class is finalized", function() {
class TestConfig extends classes.Config { }
TestConfig.finalize();
class FinalizedConfig extends classes.Config { }
FinalizedConfig.finalize();
class TestGroup extends classes.ConfigGroup { }
TestGroup.groupName = "test";
TestGroup.finalize();
assert.throws(
() => TestConfig.registerGroup(TestGroup),
() => FinalizedConfig.registerGroup(TestGroup),
new Error("Cannot register group on finalized config")
);
});
+34 -34
View File
@@ -3,24 +3,24 @@ const assert = require("assert").strict;
const fs = require("fs-extra");
const path = require("path");
const database = require("@clusterio/lib/database");
const libDatabase = require("@clusterio/lib/database");
describe("lib/database", function() {
describe("mapToObject()", function() {
it("should throw on non-string key", function() {
assert.throws(
() => database.mapToObject(new Map([[1, 1]])),
() => libDatabase.mapToObject(new Map([[1, 1]])),
new Error("Expected all keys to be string but got number")
);
assert.throws(
() => database.mapToObject(new Map([[Symbol("test"), 1]])),
() => libDatabase.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]])),
libDatabase.mapToObject(new Map([["a", 1], ["b", true]])),
{ "a": 1, "b": true }
);
});
@@ -32,7 +32,7 @@ describe("lib/database", function() {
for (let type of badTypes) {
it(`should reject on ${type} JSON`, async function() {
await assert.rejects(
database.loadJsonAsMap(path.join("test", "file", "json", `${type}.json`)),
libDatabase.loadJsonAsMap(path.join("test", "file", "json", `${type}.json`)),
new Error(`Expected object but got ${type}`)
);
});
@@ -40,21 +40,21 @@ describe("lib/database", function() {
it("should work on empty object JSON", async function() {
assert.deepEqual(
await database.loadJsonAsMap(path.join("test", "file", "json", "object.json")),
await libDatabase.loadJsonAsMap(path.join("test", "file", "json", "object.json")),
new Map()
);
});
it("should work on object JSON", async function() {
assert.deepEqual(
await database.loadJsonAsMap(path.join("test", "file", "json", "load_map.json")),
await libDatabase.loadJsonAsMap(path.join("test", "file", "json", "load_map.json")),
new Map([["a", 1], ["b", true]])
);
});
it("should give an empty Map for non-existant file", async function() {
assert.deepEqual(
await database.loadJsonAsMap(path.join("test", "file", "json", "does-not-exist")),
await libDatabase.loadJsonAsMap(path.join("test", "file", "json", "does-not-exist")),
new Map()
);
});
@@ -75,7 +75,7 @@ describe("lib/database", function() {
}
await deleteTestFile();
await database.saveMapAsJson(
await libDatabase.saveMapAsJson(
testFile, new Map([["c", {}], ["d", "foo"]])
);
@@ -92,7 +92,7 @@ describe("lib/database", function() {
for (let type of badTypes) {
it(`should reject on ${type} JSON`, async function() {
await assert.rejects(
database.loadJsonArrayAsMap(path.join("test", "file", "json", `${type}.json`)),
libDatabase.loadJsonArrayAsMap(path.join("test", "file", "json", `${type}.json`)),
new Error(`Expected array but got ${type}`)
);
});
@@ -100,35 +100,35 @@ describe("lib/database", function() {
it("should reject on null element", async function() {
await assert.rejects(
database.loadJsonArrayAsMap(path.join("test", "file", "json", "array_null.json")),
libDatabase.loadJsonArrayAsMap(path.join("test", "file", "json", "array_null.json")),
new Error("Expected all elements to be objects")
);
});
it("should reject on element missing id", async function() {
await assert.rejects(
database.loadJsonArrayAsMap(path.join("test", "file", "json", "array_object.json")),
libDatabase.loadJsonArrayAsMap(path.join("test", "file", "json", "array_object.json")),
new Error("Expected all elements to have an id property")
);
});
it("should work on empty array JSON", async function() {
assert.deepEqual(
await database.loadJsonArrayAsMap(path.join("test", "file", "json", "array.json")),
await libDatabase.loadJsonArrayAsMap(path.join("test", "file", "json", "array.json")),
new Map()
);
});
it("should work on array JSON", async function() {
assert.deepEqual(
await database.loadJsonArrayAsMap(path.join("test", "file", "json", "load_array_map.json")),
await libDatabase.loadJsonArrayAsMap(path.join("test", "file", "json", "load_array_map.json")),
new Map([["a", { id: "a" }], ["b", { id: "b" }]])
);
});
it("should give an empty Map for non-existant file", async function() {
assert.deepEqual(
await database.loadJsonArrayAsMap(path.join("test", "file", "json", "does-not-exist")),
await libDatabase.loadJsonArrayAsMap(path.join("test", "file", "json", "does-not-exist")),
new Map()
);
});
@@ -149,7 +149,7 @@ describe("lib/database", function() {
}
await deleteTestFile();
await database.saveMapAsJsonArray(
await libDatabase.saveMapAsJsonArray(
testFile, new Map([["c", { id: "c" }], ["d", { id: "d" }]])
);
@@ -163,23 +163,23 @@ describe("lib/database", function() {
describe("class ItemDatabase", function() {
describe("constructor()", function() {
it("should create an empty database with no args", function() {
let items = new database.ItemDatabase();
let items = new libDatabase.ItemDatabase();
assert.deepEqual(items._items, new Map());
});
it("should restore the passed serialized database", function() {
let items = new database.ItemDatabase({ "a": 1, "b": 2 });
let items = new libDatabase.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 libDatabase.ItemDatabase({ "a": NaN }),
new Error("count must be a number")
);
assert.throws(
() => new database.ItemDatabase({ "a": "a" }),
() => new libDatabase.ItemDatabase({ "a": "a" }),
new Error("count must be a number")
);
});
@@ -187,36 +187,36 @@ describe("lib/database", function() {
describe(".serialize()", function() {
it("should return a serialized database", function() {
let items = new database.ItemDatabase({ "a": 10 });
let items = new libDatabase.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 libDatabase.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 libDatabase.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 });
let items = new libDatabase.ItemDatabase({ "a": 10 });
assert.equal(items.getItemCount("a"), 10);
});
it("should return zero if item does not exist", function() {
let items = new database.ItemDatabase();
let items = new libDatabase.ItemDatabase();
assert.equal(items.getItemCount("b"), 0);
});
it("should throw on invalid name", function() {
let items = new database.ItemDatabase();
let items = new libDatabase.ItemDatabase();
assert.throws(
() => items.getItemCount(2),
new Error("name must be a string")
@@ -226,19 +226,19 @@ describe("lib/database", function() {
describe(".addItem()", function() {
it("should add a new item", function() {
let items = new database.ItemDatabase();
let items = new libDatabase.ItemDatabase();
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 });
let items = new libDatabase.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();
let items = new libDatabase.ItemDatabase();
assert.throws(
() => items.addItem(2, 10),
new Error("name must be a string")
@@ -246,7 +246,7 @@ describe("lib/database", function() {
});
it("should throw on invalid count", function() {
let items = new database.ItemDatabase();
let items = new libDatabase.ItemDatabase();
assert.throws(
() => items.addItem("a", NaN),
new Error("count must be a number")
@@ -260,19 +260,19 @@ describe("lib/database", function() {
describe(".removeItem()", function() {
it("should remove an existing item", function() {
let items = new database.ItemDatabase({ "a": 20 });
let items = new libDatabase.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();
let items = new libDatabase.ItemDatabase();
items.removeItem("a", 10);
assert.deepEqual(items._items, new Map([["a", -10]]));
});
it("should throw on invalid name", function() {
let items = new database.ItemDatabase();
let items = new libDatabase.ItemDatabase();
assert.throws(
() => items.removeItem(2, 10),
new Error("name must be a string")
@@ -280,7 +280,7 @@ describe("lib/database", function() {
});
it("should throw on invalid count", function() {
let items = new database.ItemDatabase();
let items = new libDatabase.ItemDatabase();
assert.throws(
() => items.removeItem("a", "b"),
new Error("count must be a number")
+3 -3
View File
@@ -2,7 +2,7 @@
const assert = require("assert").strict;
const path = require("path");
const factorio = require("@clusterio/lib/factorio");
const libFactorio = require("@clusterio/lib/factorio");
describe("lib/factorio/export", function() {
@@ -10,12 +10,12 @@ describe("lib/factorio/export", function() {
let testServer;
before(async function() {
let writePath = path.join("temp", "test", "server");
testServer = new factorio.FactorioServer(path.join("test", "file", "factorio"), writePath, {});
testServer = new libFactorio.FactorioServer(path.join("test", "file", "factorio"), writePath, {});
await testServer.init();
});
it("returns a flattened mapping with locale definitions", async function() {
let locale = await factorio._exportLocale(testServer, new Map(), ["base"], "en");
let locale = await libFactorio._exportLocale(testServer, new Map(), ["base"], "en");
assert.deepEqual(locale, new Map([["test.key-a", "1"], ["test.key-b", "2"]]));
});
});
+22 -22
View File
@@ -1,49 +1,49 @@
"use strict";
const assert = require("assert").strict;
const fs = require("fs-extra");
const jszip = require("jszip");
const JSZip = require("jszip");
const path = require("path");
const factorio = require("@clusterio/lib/factorio");
const libFactorio = require("@clusterio/lib/factorio");
describe("lib/factorio", function() {
describe("findRoot()", function() {
it("should find the root of a zip", function() {
let zip = new jszip();
let zip = new JSZip();
zip.file("root/foo.txt", "");
zip.file("root/bar.txt", "");
assert.equal(factorio._findRoot(zip), "root");
assert.equal(libFactorio._findRoot(zip), "root");
});
it("should throw if files are at the root of the zip", function() {
let zip = new jszip();
let zip = new JSZip();
zip.file("root/foo.txt", "");
zip.file("bar.txt", "");
assert.throws(
() => factorio._findRoot(zip),
() => libFactorio._findRoot(zip),
new Error("Zip contains file 'bar.txt' in root dir")
);
});
it("should throw if there are multiple root dirs", function() {
let zip = new jszip();
let zip = new JSZip();
zip.file("root-1/foo.txt", "");
zip.file("root-2/bar.txt", "");
assert.throws(
() => factorio._findRoot(zip),
() => libFactorio._findRoot(zip),
new Error("Zip contains multiple root folders")
);
});
it("should throw if given an empty zip file", function() {
let zip = new jszip();
let zip = new JSZip();
assert.throws(
() => factorio._findRoot(zip),
() => libFactorio._findRoot(zip),
new Error("Empty zip file")
);
});
@@ -79,7 +79,7 @@ describe("lib/factorio", function() {
};
it("should produce output matching the reference", function() {
assert.equal(factorio._generateLoader(patchInfo), reference);
assert.equal(libFactorio._generateLoader(patchInfo), reference);
});
});
@@ -89,7 +89,7 @@ describe("lib/factorio", function() {
{ name: "a", version: "1.0.0", dependencies: {"b": "*"} },
{ name: "b", version: "1.0.0", dependencies: {} },
];
factorio._reorderDependencies(modules);
libFactorio._reorderDependencies(modules);
assert(modules[0].name === "b", "Dependency was not reorederd");
});
it("should throw on invalid version", function() {
@@ -97,7 +97,7 @@ describe("lib/factorio", function() {
{ name: "a", version: "foo", dependencies: {} },
];
assert.throws(
() => factorio._reorderDependencies(modules),
() => libFactorio._reorderDependencies(modules),
new Error("Invalid version 'foo' for module a")
);
});
@@ -106,7 +106,7 @@ describe("lib/factorio", function() {
{ name: "a", version: "1.0.0", dependencies: {"b": "invalid"} },
];
assert.throws(
() => factorio._reorderDependencies(modules),
() => libFactorio._reorderDependencies(modules),
new Error("Invalid version range 'invalid' for dependency b on module a")
);
});
@@ -115,7 +115,7 @@ describe("lib/factorio", function() {
{ name: "a", version: "1.0.0", dependencies: {"b": "*"} },
];
assert.throws(
() => factorio._reorderDependencies(modules),
() => libFactorio._reorderDependencies(modules),
new Error("Missing dependency b for module a")
);
modules = [
@@ -124,7 +124,7 @@ describe("lib/factorio", function() {
{ name: "c", version: "1.0.0", dependencies: {"d": "*"} },
];
assert.throws(
() => factorio._reorderDependencies(modules),
() => libFactorio._reorderDependencies(modules),
new Error("Missing dependency d for module c")
);
});
@@ -134,7 +134,7 @@ describe("lib/factorio", function() {
{ name: "b", version: "1.0.0", dependencies: {"a": ">=2"} },
];
assert.throws(
() => factorio._reorderDependencies(modules),
() => libFactorio._reorderDependencies(modules),
new Error("Module b requires a >=2")
);
});
@@ -144,7 +144,7 @@ describe("lib/factorio", function() {
{ name: "b", version: "1.0.0", dependencies: {"a": "*"} },
];
assert.throws(
() => factorio._reorderDependencies(modules),
() => libFactorio._reorderDependencies(modules),
new Error("Module dependency loop detected: a -> b -> a")
);
modules = [
@@ -154,7 +154,7 @@ describe("lib/factorio", function() {
{ name: "c", version: "1.0.0", dependencies: {"a": "*"} },
];
assert.throws(
() => factorio._reorderDependencies(modules),
() => libFactorio._reorderDependencies(modules),
new Error("Module dependency loop detected: b -> c -> a -> b")
);
});
@@ -194,7 +194,7 @@ describe("lib/factorio", function() {
}
let success = false;
try {
factorio._reorderDependencies(modules);
libFactorio._reorderDependencies(modules);
success = true;
} catch (err) { }
if (success) {
@@ -211,13 +211,13 @@ describe("lib/factorio", function() {
describe("patch()", function() {
it("should throw on unknown scenario", async function() {
let zip = new jszip();
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 assert.rejects(
factorio.patch(zipPath, []),
libFactorio.patch(zipPath, []),
new Error("Unable to patch save, unknown scenario (3acc3be3861144e55604f5ac2f2555071885ebc4)")
);
});
+20 -20
View File
@@ -4,22 +4,22 @@ const events = require("events");
const fs = require("fs-extra");
const path = require("path");
const factorio = require("@clusterio/lib/factorio");
const libFactorio = require("@clusterio/lib/factorio");
const { testLines } = require("./lines");
describe("lib/factorio/server", function() {
describe("_getVersion()", function() {
it("should get the version from a changelog", async function() {
let version = await factorio._getVersion(path.join("test", "file", "changelog-test.txt"));
let version = await libFactorio._getVersion(path.join("test", "file", "changelog-test.txt"));
assert.equal(version, "0.1.1");
});
it("should return null if unable to find the version", async function() {
let version = await factorio._getVersion(path.join("test", "file", "changelog-bad.txt"));
let version = await libFactorio._getVersion(path.join("test", "file", "changelog-bad.txt"));
assert.equal(version, null);
});
it("should return null if file does not exist", async function() {
let version = await factorio._getVersion(path.join("test", "file", "does-not-exist.txt"));
let version = await libFactorio._getVersion(path.join("test", "file", "does-not-exist.txt"));
assert.equal(version, null);
});
});
@@ -27,7 +27,7 @@ describe("lib/factorio/server", function() {
describe("_versionOrder()", function() {
it("should sort an array of versions", function() {
let versions = ["1.2.3", "0.1.4", "0.1.2", "1.2.3", "0.1.5", "1.10.2"];
versions.sort(factorio._versionOrder);
versions.sort(libFactorio._versionOrder);
assert.deepEqual(
versions,
["1.10.2", "1.2.3", "1.2.3", "0.1.5", "0.1.4", "0.1.2"]
@@ -38,46 +38,46 @@ describe("lib/factorio/server", function() {
describe("_findVersion()", function() {
it("should find a given install dir with latest as target", async function() {
let installDir = path.join("test", "file", "factorio");
let [dir, version] = await factorio._findVersion(installDir, "latest");
let [dir, version] = await libFactorio._findVersion(installDir, "latest");
assert.equal(dir, path.join(installDir, "data"));
assert.equal(version, "0.1.1");
});
it("should find a given install dir with correct version as target", async function() {
let installDir = path.join("test", "file", "factorio");
let [dir, version] = await factorio._findVersion(installDir, "0.1.1");
let [dir, version] = await libFactorio._findVersion(installDir, "0.1.1");
assert.equal(dir, path.join(installDir, "data"));
assert.equal(version, "0.1.1");
});
it("should reject if the install dir version does not match target version", async function() {
let installDir = path.join("test", "file", "factorio");
await assert.rejects(
factorio._findVersion(installDir, "0.1.2"),
libFactorio._findVersion(installDir, "0.1.2"),
new Error("Factorio version 0.1.2 was requested, but install directory contains 0.1.1")
);
});
it("should search given directory for latest Factorio install", async function() {
let installDir = path.join("test", "file");
let [dir, version] = await factorio._findVersion(installDir, "latest");
let [dir, version] = await libFactorio._findVersion(installDir, "latest");
assert.equal(dir, path.join(installDir, "factorio", "data"));
assert.equal(version, "0.1.1");
});
it("should search given directory for given Factorio install", async function() {
let installDir = path.join("test", "file");
let [dir, version] = await factorio._findVersion(installDir, "0.1.1");
let [dir, version] = await libFactorio._findVersion(installDir, "0.1.1");
assert.equal(dir, path.join(installDir, "factorio", "data"));
assert.equal(version, "0.1.1");
});
it("should reject if no factorio install with the given version was found", async function() {
let installDir = path.join("test", "file");
await assert.rejects(
factorio._findVersion(installDir, "0.1.2"),
libFactorio._findVersion(installDir, "0.1.2"),
new Error("Unable to find Factorio version 0.1.2")
);
});
it("should reject if no factorio install was found", async function() {
let installDir = path.join("test", "file", "instances");
await assert.rejects(
factorio._findVersion(installDir, "latest"),
libFactorio._findVersion(installDir, "latest"),
new Error(`Unable to find any Factorio install in ${installDir}`)
);
});
@@ -85,7 +85,7 @@ describe("lib/factorio/server", function() {
describe("randomDynamicPort()", function() {
it("should return a port number", function() {
let port = factorio._randomDynamicPort();
let port = libFactorio._randomDynamicPort();
assert.equal(typeof port, "number");
assert(Number.isInteger(port));
assert(0 <= port && port < 2**16);
@@ -96,31 +96,31 @@ describe("lib/factorio/server", function() {
return (49152 <= port && port <= 65535);
}
for (let i=0; i < 20; i++) {
assert(validate(factorio._randomDynamicPort()));
assert(validate(libFactorio._randomDynamicPort()));
}
});
});
describe("generatePassword()", function() {
it("should return a string", async function() {
let password = await factorio._generatePassword(1);
let password = await libFactorio._generatePassword(1);
assert.equal(typeof password, "string");
});
it("should return a string of the given length", async function() {
let password = await factorio._generatePassword(10);
let password = await libFactorio._generatePassword(10);
assert.equal(password.length, 10);
});
it("should contain only a-z, A-Z, 0-9", async function() {
let password = await factorio._generatePassword(10);
let password = await libFactorio._generatePassword(10);
assert(/^[a-zA-Z0-9]+$/.test(password), `${password} failed test`);
});
});
describe("class LineSplitter", function() {
function createSplitter(lines) {
return new factorio._LineSplitter(line => lines.push(line.toString("utf-8")));
return new libFactorio._LineSplitter(line => lines.push(line.toString("utf-8")));
}
it("should split three lines", function() {
@@ -159,7 +159,7 @@ describe("lib/factorio/server", function() {
it("should parse the test lines", function() {
for (let [line, reference] of testLines) {
reference.source = "test";
let output = factorio._parseOutput(line, "test");
let output = libFactorio._parseOutput(line, "test");
delete output.received;
assert.deepEqual(output, reference);
}
@@ -168,7 +168,7 @@ describe("lib/factorio/server", function() {
describe("class FactorioServer", function() {
let writePath = path.join("temp", "test", "server");
let server = new factorio.FactorioServer(path.join("test", "file", "factorio"), writePath, {});
let server = new libFactorio.FactorioServer(path.join("test", "file", "factorio"), writePath, {});
describe(".init()", function() {
it("should not throw on first call", async function() {
@@ -3,10 +3,10 @@ const assert = require("assert").strict;
const fs = require("fs-extra");
const path = require("path");
const fileOps = require("@clusterio/lib/fileOps");
const libFileOps = require("@clusterio/lib/file_ops");
describe("fileOps.js", function(){
let baseDir = path.join("temp", "test", "fileOps");
describe("lib/file_ops", function() {
let baseDir = path.join("temp", "test", "file_ops");
async function setupTestingEnv() {
await fs.ensureDir(path.join(baseDir, "test", "folder"));
await fs.ensureDir(path.join(baseDir, "test", "another folder"));
@@ -17,17 +17,17 @@ describe("fileOps.js", function(){
before(setupTestingEnv);
describe("fileOps.getNewestFile()", function(){
describe("getNewestFile()", function() {
it("returns a string in a directory with files", async function() {
let newest = await fileOps.getNewestFile(path.join(baseDir, "test"));
let newest = await libFileOps.getNewestFile(path.join(baseDir, "test"));
assert.equal(typeof newest, "string");
});
it("returns null if all entries were filtered out", async function() {
let newest = await fileOps.getNewestFile(path.join(baseDir, "test"), (name) => !name.endsWith(".txt"));
let newest = await libFileOps.getNewestFile(path.join(baseDir, "test"), (name) => !name.endsWith(".txt"));
assert.equal(newest, null);
});
it("returns null if directory is empty", async function() {
let newest = await fileOps.getNewestFile(path.join(baseDir, "test", "folder"));
let newest = await libFileOps.getNewestFile(path.join(baseDir, "test", "folder"));
assert.equal(newest, null);
});
});
+3 -4
View File
@@ -2,23 +2,22 @@
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const stream = require("stream");
const hash = require("@clusterio/lib/hash");
const libHash = require("@clusterio/lib/hash");
describe("lib/hash", function() {
describe("hashStream()", function() {
it("should hash a stream of bytes", async function() {
let stream = fs.createReadStream(path.join("test", "file", "hash.txt"));
let result = await hash.hashStream(stream);
let result = await libHash.hashStream(stream);
assert.equal(result, "be417768b5c3c5c1d9bcb2e7c119196dd76b5570");
});
});
describe("hashFile()", function() {
it("should hash a file", async function() {
let result = await hash.hashFile(path.join("test", "file", "hash.txt"));
let result = await libHash.hashFile(path.join("test", "file", "hash.txt"));
assert.equal(result, "be417768b5c3c5c1d9bcb2e7c119196dd76b5570");
});
});
+7 -7
View File
@@ -1,18 +1,18 @@
"use strict";
const assert = require("assert").strict;
const helpers = require("@clusterio/lib/helpers");
const libHelpers = require("@clusterio/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");
assert.equal(libHelpers.basicType("s"), "string");
assert.equal(libHelpers.basicType(null), "null");
assert.equal(libHelpers.basicType(undefined), "undefined");
assert.equal(libHelpers.basicType([1, 2]), "array");
assert.equal(libHelpers.basicType(7), "number");
assert.equal(libHelpers.basicType({ a: 1 }), "object");
});
});
});
+3 -3
View File
@@ -3,12 +3,12 @@ const assert = require("assert").strict;
const events = require("events");
const mock = require("../../mock");
const link = require("@clusterio/lib/link");
const libLink = require("@clusterio/lib/link");
describe("lib/link/connectors", function() {
describe("class WebSocketBaseConnector", function() {
let testConnector = new link.WebSocketBaseConnector();
let testConnector = new libLink.WebSocketBaseConnector();
describe("._dropSendBufferSeq()", function() {
beforeEach(function() {
@@ -34,7 +34,7 @@ describe("lib/link/connectors", function() {
});
describe("class WebSocketClientConnector", function() {
let testConnector = new link.WebSocketClientConnector("url", 1);
let testConnector = new libLink.WebSocketClientConnector("url", 1);
testConnector._socket = new mock.MockSocket();
describe(".register()", function() {
+7 -7
View File
@@ -2,9 +2,9 @@
const assert = require("assert").strict;
const events = require("events");
const link = require("@clusterio/lib/link");
const errors = require("@clusterio/lib/errors");
const schema = require("@clusterio/lib/schema");
const libLink = require("@clusterio/lib/link");
const libErrors = require("@clusterio/lib/errors");
const libSchema = require("@clusterio/lib/schema");
const mock = require("../../mock");
@@ -15,8 +15,8 @@ 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 libLink.Link("source", "target", testConnector);
testLink.setValidator("simple", libSchema.compile({
properties: {
"data": {
type: "object",
@@ -78,7 +78,7 @@ describe("lib/link/link", function() {
it("should throw on message without validator", function() {
assert.throws(
() => testLink.processMessage({ seq: 1, type: "no_validator", data: {} }),
new errors.InvalidMessage("No validator for no_validator on source-target")
new libErrors.InvalidMessage("No validator for no_validator on source-target")
);
});
it("should throw on message failing validation", function() {
@@ -90,7 +90,7 @@ describe("lib/link/link", function() {
it("should throw on unhandled message", function() {
assert.throws(
() => testLink.processMessage({ seq: 1, type: "simple", data: { string: "a" }}),
new errors.InvalidMessage("Unhandled message simple")
new libErrors.InvalidMessage("Unhandled message simple")
);
});
});
+11 -11
View File
@@ -1,20 +1,20 @@
"use strict";
const assert = require("assert").strict;
const link = require("@clusterio/lib/link");
const errors = require("@clusterio/lib/errors");
const libLink = require("@clusterio/lib/link");
const libErrors = require("@clusterio/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 libLink.Link("source", "target", new mock.MockConnector());
let testTargetLink = new libLink.Link("target", "source", new mock.MockConnector());
let lastTargetSent;
testTargetLink.send = (type, data) => { lastTargetSent = { type, data }; };
describe("class Request", function() {
let testRequest = new link.Request({
let testRequest = new libLink.Request({
type: "test",
links: ["source-target"],
requestProperties: {
@@ -25,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 libLink.Request({ links: [], forwardTo: "invalid" }),
new Error("Invalid forwardTo value invalid")
);
});
@@ -95,7 +95,7 @@ describe("lib/link/messages", function() {
testSourceLink.waitFor = (type, condition) => ({ data: { error: "test error" }});
await assert.rejects(
testRequest.send(testSourceLink, { test: "blah" }),
new errors.RequestError("test error")
new libErrors.RequestError("test error")
);
delete testSourceLink.waitFor;
});
@@ -103,7 +103,7 @@ describe("lib/link/messages", function() {
});
describe("class Event", function() {
let testEvent = new link.Event({
let testEvent = new libLink.Event({
type: "test",
links: ["source-target"],
eventProperties: {
@@ -114,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 libLink.Event({ forwardTo: "invalid" }),
new Error("Invalid forwardTo value invalid")
);
});
it("should throw on invalid broadcastTo", function() {
assert.throws(
() => new link.Event({ broadcastTo: "invalid" }),
() => new libLink.Event({ broadcastTo: "invalid" }),
new Error("Invalid broadcastTo value invalid")
);
});
@@ -159,7 +159,7 @@ describe("lib/link/messages", function() {
describe("attachAllMessages()", function() {
it("does not throw", function() {
link.attachAllMessages(testSourceLink);
libLink.attachAllMessages(testSourceLink);
});
});
});
-31
View File
@@ -1,31 +0,0 @@
"use strict";
const assert = require("assert").strict;
const luaTools = require("@clusterio/lib/luaTools");
describe("lib/luaTools", function() {
describe("escapeString()", function() {
it("should escape backslashes", function() {
assert.equal(luaTools.escapeString("\\a"), "\\\\a");
});
it("should escape double quotes", function() {
assert.equal(luaTools.escapeString('"a"'), '\\"a\\"');
});
it("should escape single quotes", function() {
assert.equal(luaTools.escapeString("'a'"), "\\'a\\'");
});
it("should escape nul bytes", function() {
assert.equal(luaTools.escapeString("a\0b"), "a\\0b");
});
it("should escape newlines", function() {
assert.equal(luaTools.escapeString("a\nb"), "a\\nb");
});
it("should escape carriage return", function() {
assert.equal(luaTools.escapeString("a\rb"), "a\\rb");
});
it("should escape all combined", function() {
assert.equal(luaTools.escapeString("a\\b\"c'd\0e\nf\rg"), "a\\\\b\\\"c\\'d\\0e\\nf\\rg");
});
});
});
+31
View File
@@ -0,0 +1,31 @@
"use strict";
const assert = require("assert").strict;
const libLuaTools = require("@clusterio/lib/lua_tools");
describe("lib/lua_tools", function() {
describe("escapeString()", function() {
it("should escape backslashes", function() {
assert.equal(libLuaTools.escapeString("\\a"), "\\\\a");
});
it("should escape double quotes", function() {
assert.equal(libLuaTools.escapeString('"a"'), '\\"a\\"');
});
it("should escape single quotes", function() {
assert.equal(libLuaTools.escapeString("'a'"), "\\'a\\'");
});
it("should escape nul bytes", function() {
assert.equal(libLuaTools.escapeString("a\0b"), "a\\0b");
});
it("should escape newlines", function() {
assert.equal(libLuaTools.escapeString("a\nb"), "a\\nb");
});
it("should escape carriage return", function() {
assert.equal(libLuaTools.escapeString("a\rb"), "a\\rb");
});
it("should escape all combined", function() {
assert.equal(libLuaTools.escapeString("a\\b\"c'd\0e\nf\rg"), "a\\\\b\\\"c\\'d\\0e\\nf\\rg");
});
});
});
-22
View File
@@ -1,22 +0,0 @@
"use strict";
var assert = require("assert");
var objectOps = require("@clusterio/lib/objectOps");
describe("objectOps.js", function(){
describe("objectOps.deepclone()", function(){
it("deep clones javascript objects", function(){
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");
});
it("throws on non JSON parameters", function(){
assert.throws(function(){
let y = objectOps.deepclone(objectOps.deepclone);
});
});
});
});
+19 -18
View File
@@ -4,16 +4,15 @@ const fs = require("fs-extra");
const path = require("path");
const mock = require("../mock");
const link = require("@clusterio/lib/link");
const plugin = require("@clusterio/lib/plugin");
const errors = require("@clusterio/lib/errors");
const libLink = require("@clusterio/lib/link");
const libPlugin = require("@clusterio/lib/plugin");
describe("lib/plugin", function() {
describe("class BaseInstancePlugin", function() {
let instancePlugin;
it("should be constructible", async function() {
instancePlugin = new plugin.BaseInstancePlugin();
instancePlugin = new libPlugin.BaseInstancePlugin();
await instancePlugin.init();
});
it("should define defaults for hooks", async function() {
@@ -30,7 +29,7 @@ describe("lib/plugin", function() {
describe("class BaseMasterPlugin", function() {
let masterPlugin;
it("should be constructible", async function() {
masterPlugin = new plugin.BaseMasterPlugin();
masterPlugin = new libPlugin.BaseMasterPlugin();
await masterPlugin.init();
});
it("should define defaults for hooks", async function() {
@@ -68,50 +67,52 @@ describe("lib/plugin", function() {
it("should throw on missing plugin", async function() {
await assert.rejects(
plugin.loadPluginInfos(new Map([["missing", missingPlugin]]), []),
libPlugin.loadPluginInfos(new Map([["missing", missingPlugin]]), []),
new RegExp(`^Error: PluginError: Cannot find module '${missingPlugin}/info'`)
);
});
it("should load test plugin", async function() {
assert.deepEqual(
await plugin.loadPluginInfos(new Map([["test", path.resolve(testPlugin)]])),
await libPlugin.loadPluginInfos(new Map([["test", path.resolve(testPlugin)]])),
[{ name: "test", version: "0.0.1", requirePath: path.resolve(testPlugin) }]
);
});
it("should reject on broken plugin", async function() {
await assert.rejects(
plugin.loadPluginInfos(new Map([["broken", path.resolve(brokenPlugin)]])),
libPlugin.loadPluginInfos(new Map([["broken", path.resolve(brokenPlugin)]])),
{ message: "PluginError: Unexpected identifier" }
);
});
it("should reject on invalid plugin", async function() {
await assert.rejects(
plugin.loadPluginInfos(new Map([["invalid", path.resolve(invalidPlugin)]])),
libPlugin.loadPluginInfos(new Map([["invalid", path.resolve(invalidPlugin)]])),
{ message: `Expected plugin at ${path.resolve(invalidPlugin)} to be named invalid but got wrong` }
);
});
});
describe("attachPluginMessages()", function() {
let mockLink = new link.Link("source", "target", new mock.MockConnector());
let mockEvent = new link.Event({ type: "test:test", links: ["target-source"] });
let mockLink = new libLink.Link("source", "target", new mock.MockConnector());
let mockEvent = new libLink.Event({ type: "test:test", links: ["target-source"] });
it("should accept pluginInfo without messages", function() {
plugin.attachPluginMessages(mockLink, {}, null);
libPlugin.attachPluginMessages(mockLink, {}, null);
});
it("should attach handler for the given message", function() {
function mockEventEventHandler() { };
plugin.attachPluginMessages(mockLink, { name: "test", messages: { mockEvent }}, { mockEventEventHandler });
libPlugin.attachPluginMessages(
mockLink, { name: "test", messages: { mockEvent }}, { mockEventEventHandler }
);
assert(mockLink._handlers.get("test:test_event"), "handler was not registered");
});
it("should throw if missing handler for the given message", function() {
assert.throws(
() => plugin.attachPluginMessages(mockLink, { name: "test", messages: { mockEvent }}, {}),
() => libPlugin.attachPluginMessages(mockLink, { name: "test", messages: { mockEvent }}, {}),
new Error("Missing handler for test:test_event on source-target link")
);
});
it("should throw if message starts with the wrong prefix", function() {
assert.throws(
() => plugin.attachPluginMessages(mockLink, { name: "foo", messages: { mockEvent }}, {}),
() => libPlugin.attachPluginMessages(mockLink, { name: "foo", messages: { mockEvent }}, {}),
new Error('Type of mockEvent message must start with "foo:"')
);
});
@@ -132,15 +133,15 @@ describe("lib/plugin", function() {
}],
]);
it("should invoke the hook on the plugin", async function() {
await plugin.invokeHook(plugins, "test");
await libPlugin.invokeHook(plugins, "test");
assert(betaTestCalled, "Hook was not called");
});
it("should pass and return args", async function() {
let result = await plugin.invokeHook(plugins, "pass", 1234);
let result = await libPlugin.invokeHook(plugins, "pass", 1234);
assert.deepEqual(result, [1234]);
});
it("should ignore errors", async function() {
await plugin.invokeHook(plugins, "error");
await libPlugin.invokeHook(plugins, "error");
});
});
});
+8 -8
View File
@@ -87,7 +87,7 @@ describe("lib/prometheus", function() {
prometheus.defaultRegistry.unregister(collector);
}
assert(
prometheus.defaultRegistry.collectors.length == 0,
prometheus.defaultRegistry.collectors.length === 0,
"Collectors left over after removing the default ones"
);
});
@@ -367,7 +367,7 @@ describe("lib/prometheus", function() {
gauge.labels({ a: "3", b: "4" });
gauge.labels({ a: "5", b: "6" });
gauge.clear();
assert(gauge._values.size == 0, "values was not cleared");
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 });
@@ -389,7 +389,7 @@ describe("lib/prometheus", function() {
}
assert(
results.length == 1,
results.length === 1,
"collector did not give exactly one result"
);
@@ -409,7 +409,7 @@ describe("lib/prometheus", function() {
}
assert(
results.length == 1,
results.length === 1,
"collector did not give exactly one result"
);
@@ -430,7 +430,7 @@ describe("lib/prometheus", function() {
}
assert(
results.length == 1,
results.length === 1,
"collector did not give exactly one result"
);
@@ -451,7 +451,7 @@ describe("lib/prometheus", function() {
}
assert(
results.length == 1,
results.length === 1,
"collector did not give exactly one result"
);
@@ -582,7 +582,7 @@ describe("lib/prometheus", function() {
});
it("should throw on unrecoginzed option", function() {
assert.throws(
() => new prometheus.serializeResult({
() => prometheus.serializeResult({
metric: new prometheus.Metric("type", "name", "Help"),
samples: new Map([["", 3]]),
}, { invalid: true }),
@@ -604,7 +604,7 @@ describe("lib/prometheus", function() {
it("should not have any collectors in the default registry", function() {
this.skip(); // XXX Doesn't work in Clusterio's shared testing env
assert(
prometheus.defaultRegistry.collectors.length == 0,
prometheus.defaultRegistry.collectors.length === 0,
"Collectors left over by test code"
);
});
+30 -28
View File
@@ -1,34 +1,36 @@
"use strict";
const assert = require("assert").strict;
const users = require("@clusterio/lib/users");
const libUsers = require("@clusterio/lib/users");
describe("lib/users", function() {
describe("definePermission()", function() {
it("should validate the types of its arguments", function() {
assert.throws(
() => users.definePermission({ name: 123, title: "Test", description: "A test" }),
() => libUsers.definePermission({ name: 123, title: "Test", description: "A test" }),
new Error("Expected name to be a non-empty string")
);
assert.throws(
() => users.definePermission({ name: "test", title: 123, description: "A test" }),
() => libUsers.definePermission({ name: "test", title: 123, description: "A test" }),
new Error("Expected title to be a non-empty string")
);
assert.throws(
() => users.definePermission({ name: "test", title: "Test", description: 123 }),
() => libUsers.definePermission({ name: "test", title: "Test", description: 123 }),
new Error("Expected description to be a non-empty string")
);
assert.throws(
() => users.definePermission({ name: "test", title: "Test", description: "A test", grantByDefault: 1 }),
() => libUsers.definePermission({
name: "test", title: "Test", description: "A test", grantByDefault: 1,
}),
new Error("Expected grantByDefault to be a boolean")
);
});
it("should define a permission", function() {
users.definePermission({ name: "test", title: "Test", description: "A test" });
assert(users.permissions.has("test"), "Permission was not defined");
libUsers.definePermission({ name: "test", title: "Test", description: "A test" });
assert(libUsers.permissions.has("test"), "Permission was not defined");
let test = users.permissions.get("test");
let test = libUsers.permissions.get("test");
assert.equal(test.name, "test");
assert.equal(test.title, "Test");
assert.equal(test.description, "A test");
@@ -37,7 +39,7 @@ describe("lib/users", function() {
it("should throw on already defined permission", function() {
assert.throws(
() => users.definePermission({ name: "test", title: "Test", description: "A test" }),
() => libUsers.definePermission({ name: "test", title: "Test", description: "A test" }),
new Error("Permission 'test' is already defined")
);
});
@@ -45,19 +47,19 @@ describe("lib/users", function() {
describe("class Role", function() {
it("should round trip serialize", function() {
let orig = new users.Role({ id: 11, name: "Role", description: "My Role", permissions: ["test"] });
let copy = new users.Role(orig.serialize());
let orig = new libUsers.Role({ id: 11, name: "Role", description: "My Role", permissions: ["test"] });
let copy = new libUsers.Role(orig.serialize());
assert.deepEqual(copy, orig);
});
describe(".grantDefaultRoles()", function() {
it("should only grant permissions with grantByDefault", function() {
let role = new users.Role({ id: 11, name: "Role", description: "My Role" });
let role = new libUsers.Role({ id: 11, name: "Role", description: "My Role" });
role.grantDefaultPermissions();
assert(role.permissions.size > 0, "No permissions were granted");
for (let permission of role.permissions) {
assert(
users.permissions.get(permission).grantByDefault === true,
libUsers.permissions.get(permission).grantByDefault === true,
"Non-default permission granted"
);
}
@@ -67,15 +69,15 @@ describe("lib/users", function() {
describe("class User", function() {
let roles = new Map([
[1, new users.Role({ id: 1, name: "a", description: "a role", permissions: ["core.admin"] })],
[2, new users.Role({ id: 2, name: "b", description: "b role", permissions: ["test"] })],
[1, new libUsers.Role({ id: 1, name: "a", description: "a role", permissions: ["core.admin"] })],
[2, new libUsers.Role({ id: 2, name: "b", description: "b role", permissions: ["test"] })],
]);
it("should round trip serialize", function() {
function test_roundtrip(serialized) {
let user = new users.User(serialized, roles);
let user = new libUsers.User(serialized, roles);
let user_serialized = user.serialize();
assert.deepEqual(user_serialized, serialized);
let user_deserialized = new users.User(user_serialized, roles);
let user_deserialized = new libUsers.User(user_serialized, roles);
assert.deepEqual(user_deserialized, user);
}
@@ -85,40 +87,40 @@ describe("lib/users", function() {
test_roundtrip({ name: "user", is_banned: true, ban_reason: "Bad user" });
});
it("should ignore invalid roles", function() {
let user = new users.User({ name: "test", roles: [1, 4, 55] }, roles);
let user = new libUsers.User({ name: "test", roles: [1, 4, 55] }, roles);
assert.equal(user.roles.size, 1, "Unexpected count of roles");
});
it("should track online users", function() {
let user = new users.User({ name: "admin", roles: [1] }, roles);
assert(!users.User.onlineUsers.has(user));
let user = new libUsers.User({ name: "admin", roles: [1] }, roles);
assert(!libUsers.User.onlineUsers.has(user));
assert.deepEqual(user.instances, new Set());
user.notifyJoin(12);
assert(users.User.onlineUsers.has(user));
assert(libUsers.User.onlineUsers.has(user));
assert.deepEqual(user.instances, new Set([12]));
user.notifyJoin(8);
assert(users.User.onlineUsers.has(user));
assert(libUsers.User.onlineUsers.has(user));
assert.deepEqual(user.instances, new Set([12, 8]));
user.notifyLeave(11);
assert(users.User.onlineUsers.has(user));
assert(libUsers.User.onlineUsers.has(user));
assert.deepEqual(user.instances, new Set([12, 8]));
user.notifyLeave(12);
assert(users.User.onlineUsers.has(user));
assert(libUsers.User.onlineUsers.has(user));
assert.deepEqual(user.instances, new Set([8]));
user.notifyLeave(8);
assert(!users.User.onlineUsers.has(user));
assert(!libUsers.User.onlineUsers.has(user));
assert.deepEqual(user.instances, new Set());
});
describe(".checkPermission()", function() {
it("should correctly resolve permissions", function() {
let a = new users.User({ name: "admin", roles: [1] }, roles);
let b = new users.User({ name: "user", roles: [2] }, roles);
let c = new users.User({ name: "null", roles: [] }, roles);
let a = new libUsers.User({ name: "admin", roles: [1] }, roles);
let b = new libUsers.User({ name: "user", roles: [2] }, roles);
let c = new libUsers.User({ name: "null", roles: [] }, roles);
a.checkPermission("core.control.connect");
assert.throws(() => b.checkPermission("core.control.connect"), new Error("Permission denied"));
@@ -3,9 +3,9 @@ const assert = require("assert");
const fs = require("fs-extra");
const path = require("path");
const generateSSLcert = require("@clusterio/master/src/generate_ssl_cert");
const generateSslCert = require("@clusterio/master/src/generate_ssl_cert");
describe("generateSSLcert.js(options)", function() {
describe("generate_ssl_cert", function() {
let testDir = path.join("temp", "test", "certTest");
before(async function() {
await fs.remove(testDir);
@@ -21,7 +21,7 @@ describe("generateSSLcert.js(options)", function() {
let certPath = path.join(testDir, "cert.crt");
let privKeyPath = path.join(testDir, "cert.key");
await generateSSLcert({
await generateSslCert({
bits: 512, // This is too small for real world usage, but faster to test
sslCertPath: certPath,
sslPrivKeyPath: privKeyPath,
+4 -4
View File
@@ -1,7 +1,7 @@
"use strict";
const events = require("events");
const link = require("@clusterio/lib/link");
const libLink = require("@clusterio/lib/link");
class MockSocket {
@@ -77,7 +77,7 @@ class MockServer {
}
}
class MockInstance extends link.Link {
class MockInstance extends libLink.Link {
constructor() {
super("instance", "slave", new MockConnector());
this.server = new MockServer();
@@ -92,13 +92,13 @@ class MockInstance extends link.Link {
}
}
class MockSlave extends link.Link {
class MockSlave extends libLink.Link {
constructor() {
super("slave", "master", new MockConnector());
}
}
class MockControl extends link.Link {
class MockControl extends libLink.Link {
constructor(connector) {
super("control", "master", connector);
}
+9 -9
View File
@@ -3,22 +3,22 @@ const assert = require("assert").strict;
const path = require("path");
const fs = require("fs-extra");
const link = require("@clusterio/lib/link");
const config = require("@clusterio/lib/config");
const libLink = require("@clusterio/lib/link");
const libConfig = require("@clusterio/lib/config");
const slave = require("@clusterio/slave/slave");
describe("Slave testing", function() {
before(function() {
config.InstanceConfig.finalize();
libConfig.InstanceConfig.finalize();
});
describe("class Instance", function() {
let instance;
before(async function() {
let instanceConfig = new config.InstanceConfig();
let instanceConfig = new libConfig.InstanceConfig();
await instanceConfig.init();
instanceConfig.set("instance.name", "foo");
instance = new slave._Instance({}, new link.VirtualConnector(), "dir", "factorioDir", instanceConfig);
instance = new slave._Instance({}, new libLink.VirtualConnector(), "dir", "factorioDir", instanceConfig);
});
describe(".name", function() {
@@ -106,11 +106,11 @@ describe("Slave testing", function() {
await fs.outputFile(path.join(testDir, "shared", "mod_b.zip"), "b");
await fs.outputFile(path.join(testDir, "shared", "mod.dat"), "c");
let instanceConfig = new config.InstanceConfig();
let instanceConfig = new libConfig.InstanceConfig();
await instanceConfig.init();
instanceConfig.set("instance.name", "test");
instance = new slave._Instance(
{}, new link.VirtualConnector(), path.join(testDir, "instance"), "factorioDir", instanceConfig
{}, new libLink.VirtualConnector(), path.join(testDir, "instance"), "factorioDir", instanceConfig
);
await fs.outputFile(instance.path("mods", "mod_i.zip"), "i");
});
@@ -140,7 +140,7 @@ describe("Slave testing", function() {
it("should unlink removed mods", async function() {
// This does not work on Windows
if (process.platform == "win32") {
if (process.platform === "win32") {
this.skip();
}
@@ -161,7 +161,7 @@ describe("Slave testing", function() {
let instanceInfos = new Map();
await slave._discoverInstances(instanceInfos, instancePath, logger);
let referenceConfig = new config.InstanceConfig();
let referenceConfig = new libConfig.InstanceConfig();
await referenceConfig.init();
referenceConfig.set("instance.id", 1);
referenceConfig.set("instance.name", "test");