From b2a426bb306f7a11fb76f751ba928e77be8ae055 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Wed, 7 Oct 2026 01:21:11 +1100 Subject: [PATCH 1/2] Throw on a missing file when loadJsonObject is asked to Controller.loadJsonObject(path, true) returned {} for a missing file, so loadPlugins' catch never ran and a plugin whose web build was expected but missing was not reported (the second half of #1046; #1047 fixed the path). With throwOnMissing set, every error now throws; without it only a missing file is ignored, so a malformed JSON file throws again as before #1047. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01BGUULoiHNbAegwMK4PSjHJ --- CHANGELOG.md | 1 + packages/controller/src/Controller.ts | 2 +- test/controller/Controller.js | 31 +++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52c13d77..d9f3836d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Many thanks to the following for contributing to this release: ### Fixes +- A plugin whose web build is expected but missing is reported again: `Controller.loadJsonObject(path, true)` throws on a missing file instead of returning `{}`, and a malformed JSON file always throws. [#1046]() - Allow player online autosave slots to be disabled while autosaves are enabled. [#966]() - Silenced two Ant Design console warnings in the Web UI: the deprecated Modal `destroyOnClose` prop, and a duplicate-value warning from the instance version selector. [#940]() - Surface the mod portal's own error when downloading mods for an unsupported Factorio version. [#941]() diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index 6d2c0426..75a0e084 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -842,7 +842,7 @@ export default class Controller { try { manifest = JSON.parse(await fs.readFile(filePath, { encoding: "utf8" })); } catch (err: any) { - if (throwOnMissing && err.code !== "ENOENT") { + if (throwOnMissing || err.code !== "ENOENT") { throw err; } } diff --git a/test/controller/Controller.js b/test/controller/Controller.js index c0fc29e6..efcc69ee 100644 --- a/test/controller/Controller.js +++ b/test/controller/Controller.js @@ -1,3 +1,5 @@ +import fs from "node:fs/promises"; +import os from "node:os"; import path from "node:path"; import assert from "node:assert/strict"; import { Controller, HostRecord, InstanceRecord, UserRecord } from "@clusterio/controller"; @@ -55,6 +57,35 @@ describe("controller/src/Controller", function() { assert(list.check(ip), `${ip} missing from list`); } } + describe(".loadJsonObject()", function() { + let dir; + before(async function() { + dir = await fs.mkdtemp(path.join(os.tmpdir(), "clusterio-load-json-")); + await fs.writeFile(path.join(dir, "valid.json"), JSON.stringify({ "main.js": "main.123.js" })); + await fs.writeFile(path.join(dir, "invalid.json"), "{ not json"); + }); + after(async function() { + await fs.rm(dir, { recursive: true, force: true }); + }); + it("should parse an existing file", async function() { + assert.deepEqual( + await Controller.loadJsonObject(path.join(dir, "valid.json")), + { "main.js": "main.123.js" }, + ); + }); + it("should return an empty object for a missing file by default", async function() { + assert.deepEqual(await Controller.loadJsonObject(path.join(dir, "missing.json")), {}); + }); + it("should throw for a missing file when throwOnMissing is set", async function() { + await assert.rejects( + Controller.loadJsonObject(path.join(dir, "missing.json"), true), + { code: "ENOENT" }, + ); + }); + it("should throw for invalid JSON", async function() { + await assert.rejects(Controller.loadJsonObject(path.join(dir, "invalid.json")), SyntaxError); + }); + }); describe(".serveWeb()", function() { let server, port; before(async function() { From 6626b1ce7d37ca47c90445885357baa10d437b95 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Wed, 7 Oct 2026 01:44:54 +1100 Subject: [PATCH 2/2] Move the changelog entry to the PR description Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01BGUULoiHNbAegwMK4PSjHJ --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f3836d..52c13d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,6 @@ Many thanks to the following for contributing to this release: ### Fixes -- A plugin whose web build is expected but missing is reported again: `Controller.loadJsonObject(path, true)` throws on a missing file instead of returning `{}`, and a malformed JSON file always throws. [#1046]() - Allow player online autosave slots to be disabled while autosaves are enabled. [#966]() - Silenced two Ant Design console warnings in the Web UI: the deprecated Modal `destroyOnClose` prop, and a duplicate-value warning from the instance version selector. [#940]() - Surface the mod portal's own error when downloading mods for an unsupported Factorio version. [#941]()