diff --git a/packages/host/src/export.ts b/packages/host/src/export.ts index 5df9d1df..ad9aaaa3 100644 --- a/packages/host/src/export.ts +++ b/packages/host/src/export.ts @@ -413,7 +413,13 @@ async function exportIcons( state.metadata[baseName] ??= metadata; let count = 0; for (let prototype of prototypes) { - const icon = await exportIcon(server, modVersions, zipCache, state, prototype); + let icon: lib.ExportMetadataEntry["icon"]; + try { + icon = await exportIcon(server, modVersions, zipCache, state, prototype); + } catch (err: any) { + // One broken icon should not stop the rest of the export + server._logger.warn(`Skipping icon of ${prototype.type} ${prototype.name}: ${err.message}`); + } if (icon) { count += 1; } @@ -484,6 +490,7 @@ async function exportLocale( modZip = await loadZip(server, modVersions, zipCache, mod); } catch (err: any) { if (err.code === "ENOENT") { + server._logger.warn(`Skipping locale of ${mod}, its mod zip was not found`); continue; } throw err; @@ -635,6 +642,8 @@ async function buildExport(server: FactorioServer, zipCache: ZipCache) { // For testing only +export const _createSheetState = createSheetState; +export const _exportIcons = exportIcons; export const _exportLocale = exportLocale; export const _loadSimpleIcon = loadSimpleIcon; export const _loadLayeredIcon = loadLayeredIcon; diff --git a/test/host/export.js b/test/host/export.js index 43109532..4d85dd6e 100644 --- a/test/host/export.js +++ b/test/host/export.js @@ -2,7 +2,9 @@ import assert from "node:assert/strict"; import path from "node:path"; import { FactorioServer } from "@clusterio/host/dist/node/src/server.js"; -import { _exportLocale, _loadLayeredIcon, _loadSimpleIcon } from "@clusterio/host/dist/node/src/export.js"; +import { + _createSheetState, _exportIcons, _exportLocale, _loadLayeredIcon, _loadSimpleIcon, +} from "@clusterio/host/dist/node/src/export.js"; describe("host/src/export", function() { @@ -12,7 +14,25 @@ describe("host/src/export", function() { testServer = await FactorioServer.fromOptions(path.join("test", "file", "factorio"), writePath, {}); }); + let warnings; + let originalLogger; + beforeEach(function() { + warnings = []; + originalLogger = testServer._logger; + testServer._logger = Object.create(originalLogger, { warn: { value: message => warnings.push(message) } }); + }); + afterEach(function() { + testServer._logger = originalLogger; + }); + describe("exportLocale()", function() { + it("warns about mods without a zip and exports the rest", async function() { + const locale = await _exportLocale( + testServer, new Map([["missing_mod", "1.0.0"]]), new Map(), ["base", "missing_mod"], "en" + ); + assert.equal(locale.size > 0, true); + assert.deepEqual(warnings, ["Skipping locale of missing_mod, its mod zip was not found"]); + }); it("returns a flattened mapping with locale definitions", async function() { let locale = await _exportLocale(testServer, new Map(), new Map(), ["base"], "en"); assert.deepEqual(locale, new Map([ @@ -30,6 +50,28 @@ describe("host/src/export", function() { // test-icon.png is a red 64px icon followed by a blue 32px mipmap const red = 0xff0000ff; + describe("exportIcons()", function() { + it("skips icons that fail to load and exports the rest", async function() { + const state = await _createSheetState(); + const icon = "__base__/graphics/icons/test-icon.png"; + const prototypes = [ + { type: "item", name: "first", icon, icon_size: 64 }, + { type: "item", name: "no-layers", icons: [] }, + { type: "item", name: "unknown-mod", icon: "__not-a-mod__/graphics/icons/x.png", icon_size: 64 }, + { type: "item", name: "last", icon, icon_size: 64 }, + ]; + const count = await _exportIcons(testServer, new Map(), new Map(), prototypes, "item", state); + assert.equal(count, 2); + assert.deepEqual( + state.metadata.item.map(entry => [entry.name, Boolean(entry.icon)]), + [["first", true], ["no-layers", false], ["unknown-mod", false], ["last", true]], + ); + assert.equal(warnings.length, 2); + assert.match(warnings[0], /^Skipping icon of item no-layers: /); + assert.match(warnings[1], /^Skipping icon of item unknown-mod: Got path for unknown mod not-a-mod/); + }); + }); + describe("loadSimpleIcon()", function() { it("defaults icon_size to 64", async function() { const icon = await _loadSimpleIcon(