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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGUULoiHNbAegwMK4PSjHJ
This commit is contained in:
ILLISISandClaude Opus 5.5 committed 2026-10-07 01:21:11 +11:00
1 parent d89a6e33f3
commit b2a426bb30
3 files changed
+33 -1

No files matched your search

+1
View File
@@ -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](<https://github.com/clusterio/clusterio/issues/1046>)
- Allow player online autosave slots to be disabled while autosaves are enabled. [#966](<https://github.com/clusterio/clusterio/issues/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](<https://github.com/clusterio/clusterio/pull/940>)
- Surface the mod portal's own error when downloading mods for an unsupported Factorio version. [#941](<https://github.com/clusterio/clusterio/pull/941>)
+1 -1
View File
@@ -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;
}
}
+31
View File
@@ -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() {