diff --git a/docs/contributing.md b/docs/contributing.md index d8729a33..34ac895d 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -72,6 +72,8 @@ The documentation uses the binaries provided by the packages, but for the develo npx clusteriohost -> node packages/host npx clusterioctl -> node packages/ctl +By default node does not enable source maps, to get stack traces pointing to the `.ts` files you will need to pass `--enable-source-maps` to `node` or set the environment variable `NODE_OPTIONS=--enable-source-maps`. + Note that to use `clusterioctl` you will have to create a control config first: node packages/controller bootstrap create-ctl-config [admin-user] diff --git a/packages/controller/controller.ts b/packages/controller/controller.ts index 2d9a849f..726e7c11 100644 --- a/packages/controller/controller.ts +++ b/packages/controller/controller.ts @@ -260,6 +260,7 @@ async function initialize(): Promise { levels, }); clusterLogger.add(new DailyRotateFile({ + format: winston.format.json(), filename: "cluster-%DATE%.log", utc: true, dirname: path.join(args.logDirectory, "cluster"), @@ -269,6 +270,7 @@ async function initialize(): Promise { logger.add(new DailyRotateFile({ format: winston.format.json(), filename: "controller-%DATE%.log", + utc: true, dirname: path.join(args.logDirectory, "controller"), })); logger.add(new winston.transports.Stream({ diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index 29249626..580c02e8 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -265,7 +265,7 @@ export default class Controller { if (args.dev) { // eslint-disable-next-line node/no-missing-require - webpackConfigs.push(require("../../../webpack.config")({})); // Path outside of build + webpackConfigs.push(require("../../webpack.config")({})); // Path outside of build } if (args.devPlugin) { let devPlugins = new Map(); diff --git a/packages/create/create.js b/packages/create/create.js index cacb19ef..4c252f93 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -375,17 +375,26 @@ async function writeScripts(mode) { if (process.platform === "win32") { await safeOutputFile( "run-controller.cmd", - "@echo off\n.\\node_modules\\.bin\\clusteriocontroller.cmd run\n" + `\ +@echo off. +set "NODE_OPTIONS=--enable-source-maps %NODE_OPTIONS%" +\\node_modules\\.bin\\clusteriocontroller.cmd run +` ); } else { await safeOutputFile( "run-controller.sh", - "#!/bin/sh\nexec ./node_modules/.bin/clusteriocontroller run\n", + `\ +#!/bin/sh +export "NODE_OPTIONS=--enable-source-maps $NODE_OPTIONS" +exec ./node_modules/.bin/clusteriocontroller run +`, { mode: 0o755 }, ); await safeOutputFile( "systemd/clusteriocontroller.service", - `[Unit] + `\ +[Unit] Description=Clusterio Controller [Service] @@ -394,6 +403,7 @@ Group=${await groupIdToName(os.userInfo().gid)} WorkingDirectory=${process.cwd()} KillMode=mixed KillSignal=SIGINT +Environment=NODE_OPTIONS=--enable-source-maps ExecStart=${process.cwd()}/node_modules/.bin/clusteriocontroller run --log-level=warn [Install] diff --git a/packages/host/src/Host.ts b/packages/host/src/Host.ts index 602d1217..c834b25b 100644 --- a/packages/host/src/Host.ts +++ b/packages/host/src/Host.ts @@ -214,6 +214,9 @@ class HostRouter { ); } logger.warn(`Failed to deliver ${(message as any).name || "message"} (${message.type}): ${err.message}`); + if (err instanceof lib.InvalidMessage && err.errors) { + logger.warn(JSON.stringify(err.errors, null, 4)); + } } } @@ -470,7 +473,7 @@ export default class Host extends lib.Link { let instanceConnection = this.instanceConnections.get(instanceId); this.send( new lib.InstanceStatusChangedEvent( - instanceId, instanceConnection ? instanceConnection.status : "stopped", undefined + instanceId, instanceConnection ? instanceConnection.instance.status : "stopped", undefined ) ); @@ -490,7 +493,7 @@ export default class Host extends lib.Link { let instanceInfo = this.instanceInfos.get(instanceId); if (instanceInfo) { let instanceConnection = this.instanceConnections.get(instanceId); - if (instanceConnection && ["starting", "running"].includes(instanceConnection.status)) { + if (instanceConnection && ["starting", "running"].includes(instanceConnection.instance.status)) { await instanceConnection.send(new lib.InstanceStopRequest()); } @@ -530,10 +533,10 @@ export default class Host extends lib.Link { let hostAddress = new lib.Address(lib.Address.host, this.config.get("host.id")); let instanceAddress = new lib.Address(lib.Address.instance, instanceId); let [connectionClient, connectionServer] = lib.VirtualConnector.makePair(instanceAddress, hostAddress); - let instanceConnection = new InstanceConnection(connectionServer, this, instanceId); let instance = new Instance( this, connectionClient, instanceInfo.path, this.config.get("host.factorio_directory"), instanceInfo.config ); + let instanceConnection = new InstanceConnection(connectionServer, this, instance); this.instanceConnections.set(instanceId, instanceConnection); await instance.init(this.pluginInfos); @@ -750,13 +753,6 @@ export default class Host extends lib.Link { }); } - async stopInstance(instanceId: number) { - let instanceConnection = this.instanceConnections.get(instanceId); - if (instanceConnection) { - await instanceConnection.send(new lib.InstanceStopRequest()); - } - } - async handleInstanceDeleteInternalRequest(request: lib.InstanceDeleteInternalRequest) { let instanceId = request.instanceId; if (this.instanceConnections.has(instanceId)) { @@ -786,7 +782,7 @@ export default class Host extends lib.Link { let instanceConnection = this.instanceConnections.get(instanceId); list.push(new lib.RawInstanceInfo( instanceInfo.config.serialize("controller"), - instanceConnection ? instanceConnection.status : "stopped", + instanceConnection ? instanceConnection.instance.status : "stopped", )); } await this.send(new lib.InstancesUpdateRequest(list)); @@ -828,9 +824,9 @@ export default class Host extends lib.Link { } this._shuttingDown = true; - for (let instanceId of this.instanceConnections.keys()) { + for (let instanceConnection of this.instanceConnections.values()) { try { - await this.stopInstance(instanceId); + await instanceConnection.instance.stop(); } catch (err: any) { logger.error(`Unexpected error stopping instance:\n${err.stack}`); } diff --git a/packages/host/src/Instance.ts b/packages/host/src/Instance.ts index dd9e7e21..50965248 100644 --- a/packages/host/src/Instance.ts +++ b/packages/host/src/Instance.ts @@ -592,7 +592,11 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " "); * factorio_settings config entry. */ async writeServerSettings() { - let serverSettings = await this.resolveServerSettings(this.config.get("factorio.settings")); + const warning = "Changes to this file will be overwitten by the factorio.settings config on the instance."; + const serverSettings = { + "_comment_warning": warning, + ...await this.resolveServerSettings(this.config.get("factorio.settings")), + }; await lib.safeOutputFile( this.server.writePath("server-settings.json"), JSON.stringify(serverSettings, null, 4) diff --git a/packages/host/src/InstanceConnection.ts b/packages/host/src/InstanceConnection.ts index 57780263..e9f01053 100644 --- a/packages/host/src/InstanceConnection.ts +++ b/packages/host/src/InstanceConnection.ts @@ -5,12 +5,11 @@ import type Instance from "./Instance"; export default class InstanceConnection extends lib.Link { plugins = new Map(); - status: Instance["status"] = "stopped"; constructor( connector: lib.VirtualConnector, public host: Host, - public instanceId: number + public instance: Instance, ) { super(connector); this.router = this.host.router; @@ -24,9 +23,8 @@ export default class InstanceConnection extends lib.Link { } async snoopInstanceStatusChangedEvent(event: lib.InstanceStatusChangedEvent) { - this.status = event.status as Instance["status"]; - if (this.status === "stopped") { - this.host.instanceConnections.delete(this.instanceId); + if (this.instance.status === "stopped") { + this.host.instanceConnections.delete(this.instance.id); } } } diff --git a/packages/lib/src/ini.ts b/packages/lib/src/ini.ts index 592d7fb1..ecf11052 100644 --- a/packages/lib/src/ini.ts +++ b/packages/lib/src/ini.ts @@ -11,6 +11,11 @@ * @returns sections parsed from the ini file. */ export function parse(input: string) { + // Ignore BOM at the start of the file + if (input[0] === "\uFEFF") { + input = input.slice(1); + } + const sections: Record> = {}; let currentSection: Record = sections as Record; const lines = input.split(/\r\n|\n/); diff --git a/packages/lib/src/link/link.ts b/packages/lib/src/link/link.ts index 998e3a63..ebc5cecf 100644 --- a/packages/lib/src/link/link.ts +++ b/packages/lib/src/link/link.ts @@ -11,8 +11,8 @@ import type { User } from "../users"; import type { AddressType, JSONDeserialisable, MessageRoutable, MessageRequest, MessageEvent } from "../data"; export interface Request { - constructor: Partial>> & { - new (...args: any): Request, + constructor: Partial>> & { + new (...args: any): Req & Request, name: string, type: "request"; src: AddressType | readonly AddressType[]; @@ -25,8 +25,8 @@ export interface Request { export type RequestClass = Request["constructor"]; export interface Event { - constructor: Partial>> & { - new (...args: any): Event, + constructor: Partial>> & { + new (...args: any): T & Event, name: string, type: "event"; src: AddressType | readonly AddressType[]; diff --git a/packages/lib/src/subscriptions.ts b/packages/lib/src/subscriptions.ts index eb82e02e..ab7b4c7f 100644 --- a/packages/lib/src/subscriptions.ts +++ b/packages/lib/src/subscriptions.ts @@ -215,7 +215,7 @@ export class SubscriptionController { * Channels are an optional feature which can be implemented to only get notifications of reliant updates * Multiple handlers can be subscribed at the same time, on the same or different channels */ -export class EventSubscriber { +export class EventSubscriber, V=T> { _callbacks = new Array>(); _channelCallbacks = new Map>>(); lastResponse: Event | null = null; @@ -365,7 +365,7 @@ export class EventSubscriber { )); if (response.eventReplay) { - await this._handle(response.eventReplay); + await this._handle(response.eventReplay as any); } } } diff --git a/test/file/bom.cfg b/test/file/bom.cfg new file mode 100644 index 00000000..e559eb73 --- /dev/null +++ b/test/file/bom.cfg @@ -0,0 +1,2 @@ +[spam] +foo=bar diff --git a/test/lib/ini.js b/test/lib/ini.js index c7cd39f5..6b0d9697 100644 --- a/test/lib/ini.js +++ b/test/lib/ini.js @@ -1,5 +1,6 @@ "use strict"; const assert = require("assert").strict; +const fs = require("fs-extra"); const lib = require("@clusterio/lib"); describe("lib/ini", function() { @@ -58,6 +59,12 @@ describe("lib/ini", function() { new Error("Duplicated key key on line 2") ); }); + it("should parse a file with BOM", async function() { + assert.deepEqual( + lib.parse(await fs.readFile("test/file/bom.cfg", "utf8")), + { spam: { foo: "bar" }}, + ); + }); }); describe("stringify()", function() { it("should serialize to reference output", function() {