mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 23:24:52 +09:00
Add create-scenario command to clusteriohost
Creates a scenario directory with the Clusterio modules patched into it so the modules can be used outside of Clusterio. The command reuses the save patching by zipping the scenario, running patch() on it and extracting the result. Defaults to the freeplay scenario from the Factorio install in the host config and all plugins enabled there. Module loading in Instance.prepareSave is moved to patch.loadModules so the command and the instance share it, and findVersion is exported from server.ts so the command can find the freeplay scenario. Zipped scenarios were tried as an output form but Factorio does not pick them up from the scenarios folder, so only directories are written. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
7987a5d925
commit
c419f3e9b2
10 files changed
+349
-41
No files matched your search
@@ -0,0 +1,93 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const fs = require("node:fs/promises");
|
||||
const JSZip = require("jszip");
|
||||
const path = require("path");
|
||||
|
||||
const lib = require("@clusterio/lib");
|
||||
const { createScenario } = require("@clusterio/host/dist/node/src/create_scenario");
|
||||
const { SaveModule, PatchInfo } = require("@clusterio/host/dist/node/src/patch");
|
||||
|
||||
|
||||
describe("host/create_scenario", function() {
|
||||
const tempDir = path.join("temp", "test", "create_scenario");
|
||||
const patchInfo = new PatchInfo(0, new lib.ModuleInfo("test", "1.0.0", [], ["scenario"]), []);
|
||||
const scenarioFiles = new Map([
|
||||
["scenario.lua", "-- scenario\n"],
|
||||
["description.json", "{}\n"],
|
||||
["locale/en/test.cfg", "test=Test\n"],
|
||||
["clusterio.json", JSON.stringify(patchInfo)],
|
||||
]);
|
||||
const module = new SaveModule(
|
||||
new lib.ModuleInfo("spam", "1.0.0", ["spam.lua"]),
|
||||
new Map([["modules/spam/spam.lua", Buffer.from("return {}\n")]]),
|
||||
);
|
||||
|
||||
async function readDir(dirPath) {
|
||||
const files = new Map();
|
||||
for (const entry of await fs.readdir(dirPath, { recursive: true, withFileTypes: true })) {
|
||||
if (entry.isFile()) {
|
||||
const fsPath = path.join(entry.parentPath, entry.name);
|
||||
files.set(path.relative(dirPath, fsPath).split(path.sep).join("/"), await fs.readFile(fsPath, "utf8"));
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function checkOutput(files) {
|
||||
assert(files.get("control.lua").includes('event_handler.add_lib(require("modules/spam/spam"))'));
|
||||
assert.equal(JSON.parse(files.get("clusterio.json")).patch_number, 1);
|
||||
assert.equal(files.get("modules/spam/spam.lua"), "return {}\n");
|
||||
for (const name of ["scenario.lua", "description.json", "locale/en/test.cfg"]) {
|
||||
assert.equal(files.get(name), scenarioFiles.get(name));
|
||||
}
|
||||
assert.equal(files.size, scenarioFiles.size + 2);
|
||||
}
|
||||
|
||||
before(async function() {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
await fs.mkdir(path.join(tempDir, "scenario"), { recursive: true });
|
||||
const zip = new JSZip();
|
||||
for (const [name, content] of scenarioFiles) {
|
||||
const fsPath = path.join(tempDir, "scenario", name);
|
||||
await fs.mkdir(path.dirname(fsPath), { recursive: true });
|
||||
await fs.writeFile(fsPath, content);
|
||||
zip.file(`scenario/${name}`, content);
|
||||
}
|
||||
await fs.writeFile(path.join(tempDir, "scenario.zip"), await zip.generateAsync({ type: "nodebuffer" }));
|
||||
});
|
||||
|
||||
it("should patch a scenario directory", async function() {
|
||||
const output = path.join(tempDir, "from_dir");
|
||||
await createScenario(path.join(tempDir, "scenario"), output, [module]);
|
||||
checkOutput(await readDir(output));
|
||||
});
|
||||
|
||||
it("should patch a zipped scenario", async function() {
|
||||
const output = path.join(tempDir, "from_zip");
|
||||
await createScenario(path.join(tempDir, "scenario.zip"), output, [module]);
|
||||
checkOutput(await readDir(output));
|
||||
});
|
||||
|
||||
it("should not modify the input scenario", async function() {
|
||||
const files = await readDir(path.join(tempDir, "scenario"));
|
||||
assert.deepEqual(files, scenarioFiles);
|
||||
});
|
||||
|
||||
it("should throw if the output already exists", async function() {
|
||||
const output = path.join(tempDir, "from_dir");
|
||||
await assert.rejects(
|
||||
createScenario(path.join(tempDir, "scenario"), output, [module]),
|
||||
new Error(`${output} already exists`),
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw on unknown scenario", async function() {
|
||||
await fs.mkdir(path.join(tempDir, "unknown"));
|
||||
await fs.writeFile(path.join(tempDir, "unknown", "control.lua"), "-- unknown\n");
|
||||
await assert.rejects(
|
||||
createScenario(path.join(tempDir, "unknown"), path.join(tempDir, "from_unknown"), [module]),
|
||||
new Error("Unable to patch save, unknown scenario (3acc3be3861144e55604f5ac2f2555071885ebc4)")
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -226,6 +226,22 @@ describe("host/patch", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadModules()", function() {
|
||||
it("should load the bundled clusterio module", async function() {
|
||||
const modules = await patch.loadModules([]);
|
||||
assert.deepEqual([...modules.keys()], ["clusterio"]);
|
||||
assert(modules.get("clusterio").files.has("modules/clusterio/api.lua"));
|
||||
});
|
||||
it("should load modules from plugins", async function() {
|
||||
const pluginInfos = await lib.loadPluginInfos(
|
||||
new Map([["research_sync", path.resolve("plugins/research_sync")]])
|
||||
);
|
||||
const modules = await patch.loadModules(pluginInfos);
|
||||
assert.deepEqual([...modules.keys()], ["research_sync", "clusterio"]);
|
||||
assert(modules.get("research_sync").files.has("modules/research_sync/sync.lua"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("patch()", function() {
|
||||
it("should throw on unknown scenario", async function() {
|
||||
let zip = new JSZip();
|
||||
|
||||
+9
-9
@@ -42,26 +42,26 @@ describe("host/server", function() {
|
||||
describe("direct install", function() {
|
||||
it("should search given directory for latest Factorio install", async function() {
|
||||
const installDir = path.join("test", "file", "factorio", "0.1.2");
|
||||
const [dir, version] = await hostServer._findVersion(installDir, "latest");
|
||||
const [dir, version] = await hostServer.findVersion(installDir, "latest");
|
||||
assert.equal(dir, path.join(installDir, "data"));
|
||||
assert.equal(version, "0.1.2");
|
||||
});
|
||||
it("should search given directory for given Factorio install", async function() {
|
||||
const installDir = path.join("test", "file", "factorio", "0.1.1");
|
||||
const [dir, version] = await hostServer._findVersion(installDir, "0.1.1");
|
||||
const [dir, version] = await hostServer.findVersion(installDir, "0.1.1");
|
||||
assert.equal(dir, path.join(installDir, "data"));
|
||||
assert.equal(version, "0.1.1");
|
||||
});
|
||||
it("should search given directory for partly given Factorio install", async function() {
|
||||
const installDir = path.join("test", "file", "factorio", "0.1.2");
|
||||
const [dir, version] = await hostServer._findVersion(installDir, "0.1");
|
||||
const [dir, version] = await hostServer.findVersion(installDir, "0.1");
|
||||
assert.equal(dir, path.join(installDir, "data"));
|
||||
assert.equal(version, "0.1.2");
|
||||
});
|
||||
it("should reject if the version does not match", async function() {
|
||||
let installDir = path.join("test", "file", "factorio", "0.1.1");
|
||||
await assert.rejects(
|
||||
hostServer._findVersion(installDir, "0.1.2"),
|
||||
hostServer.findVersion(installDir, "0.1.2"),
|
||||
new Error(
|
||||
`Unable to find Factorio version 0.1.2: ${installDir} is a direct (single-version) ` +
|
||||
`install of 0.1.1. Use a versioned layout, where ${installDir} contains a subdirectory ` +
|
||||
@@ -74,33 +74,33 @@ describe("host/server", function() {
|
||||
it("should reject if no factorio install with the given version was found", async function() {
|
||||
let installDir = path.join("test", "file", "factorio");
|
||||
await assert.rejects(
|
||||
hostServer._findVersion(installDir, "0.1.3"),
|
||||
hostServer.findVersion(installDir, "0.1.3"),
|
||||
new Error("Unable to find Factorio version 0.1.3")
|
||||
);
|
||||
});
|
||||
it("should search given directory for given Factorio install", async function() {
|
||||
const installDir = path.join("test", "file", "factorio");
|
||||
const [dir, version] = await hostServer._findVersion(installDir, "0.1.1");
|
||||
const [dir, version] = await hostServer.findVersion(installDir, "0.1.1");
|
||||
assert.equal(dir, path.join(installDir, "0.1.1", "data"));
|
||||
assert.equal(version, "0.1.1");
|
||||
});
|
||||
it("should search given directory for partly given Factorio install", async function() {
|
||||
const installDir = path.join("test", "file", "factorio");
|
||||
const [dir, version] = await hostServer._findVersion(installDir, "0.1");
|
||||
const [dir, version] = await hostServer.findVersion(installDir, "0.1");
|
||||
assert.equal(dir, path.join(installDir, "0.1.2", "data"));
|
||||
assert.equal(version, "0.1.2");
|
||||
});
|
||||
it("should reject if no factorio install with the given version was found", async function() {
|
||||
let installDir = path.join("test", "file", "factorio");
|
||||
await assert.rejects(
|
||||
hostServer._findVersion(installDir, "0.1.3"),
|
||||
hostServer.findVersion(installDir, "0.1.3"),
|
||||
new Error("Unable to find Factorio version 0.1.3")
|
||||
);
|
||||
});
|
||||
it("should reject if no factorio install was found", async function() {
|
||||
let installDir = path.join("test", "file");
|
||||
await assert.rejects(
|
||||
hostServer._findVersion(installDir, "0.0.0"),
|
||||
hostServer.findVersion(installDir, "0.0.0"),
|
||||
new Error(`Unable to find any Factorio install in ${installDir}`)
|
||||
);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user