Merge pull request #831 from Cooldude2606/feature/automatic-factorio-download

Add automatic headless download and update on linux
This commit is contained in:
Daniel authored and GitHub committed 2026-02-19 21:46:35 +01:00
commit 385affbcaf
21 files changed
+639 -83

No files matched your search

@@ -81,6 +81,7 @@ export default class HostConnection extends BaseConnection {
this.handle(lib.InstanceSaveDetailsUpdatesEvent, this.handleInstanceSaveDetailsUpdatesEvent.bind(this));
this.handle(lib.LogMessageEvent, this.handleLogMessageEvent.bind(this));
this.handle(lib.InstancePlayerUpdateEvent, this.handleInstancePlayerUpdateEvent.bind(this));
this.handle(lib.FactorioVersionsRequest, this.handleFactorioVersionsRequest.bind(this));
this.snoopEvent(lib.InstanceAdminlistUpdateEvent, this.snoopInstanceAdminlistUpdateEvent.bind(this));
this.snoopEvent(lib.InstanceBanlistUpdateEvent, this.snoopInstanceBanlistUpdateEvent.bind(this));
this.snoopEvent(lib.InstanceWhitelistUpdateEvent, this.snoopInstanceWhitelistUpdateEvent.bind(this));
@@ -363,6 +364,10 @@ export default class HostConnection extends BaseConnection {
});
}
async handleFactorioVersionsRequest(request: lib.FactorioVersionsRequest) {
return await this._controller.factorioVersions.get(request.maxAgeMs);
}
async snoopInstanceAdminlistUpdateEvent(event: lib.InstanceAdminlistUpdateEvent, src: lib.Address) {
if (src.type !== lib.Address.instance) {
return;
+2
View File
@@ -639,8 +639,10 @@ end`.replace(/\r?\n/g, " ");
async init(pluginInfos: lib.PluginNodeEnvInfo[]) {
this.notifyStatus("starting");
const factorioVersions = await this.sendTo("controller", new lib.FactorioVersionsRequest());
try {
await this._loadStats();
await this.server.checkForUpdates(factorioVersions);
await this.server.init();
} catch (err) {
this.notifyExit();
+176 -31
View File
@@ -2,6 +2,7 @@
import fs from "fs-extra";
import child_process from "child_process";
import path from "path";
import JSZip from "jszip";
import events from "events";
import util from "util";
import crypto from "crypto";
@@ -9,16 +10,17 @@ import { Rcon } from "rcon-client";
import * as lib from "@clusterio/lib";
const execFileAsync = util.promisify(child_process.execFile);
/**
* Determines the version of Factorio the datadir is pointing to by
* reading the changelog.txt in it.
*
* @param changelogPath - Path to changelog.txt.
* @internal
* @param factorioDir - Path factorio dir containing data/changelog.txt.
*/
async function getVersion(changelogPath: string) {
async function getFactorioVersion(factorioDir: string) {
let changelog;
const changelogPath = path.join(factorioDir, "data", "changelog.txt");
try {
changelog = await fs.readFile(changelogPath, "utf-8");
} catch (err: any) {
@@ -28,12 +30,13 @@ async function getVersion(changelogPath: string) {
throw err;
}
for (let line of changelog.split(/[\r\n]+/)) {
let index = line.indexOf(":");
for (const line of changelog.split(/[\r\n]+/)) {
const index = line.indexOf(":");
if (index !== -1) {
let name = line.slice(0, index).trim();
const name = line.slice(0, index).trim();
if (name.toLowerCase() === "version") {
return line.slice(index + 1).trim();
const version = line.slice(index + 1).trim();
return lib.isFullVersion(version) ? version : null;
}
}
}
@@ -66,13 +69,122 @@ function versionOrder(a: string, b: string) {
return 0;
}
/**
* Find all installed factorio versions and return them as a set
*
* @param factorioDir - Path to Factorio installation dirs.
* @returns Set of installed factorio versions
*/
async function listFactorioVersions(factorioDir: string) {
const versions = new Set<lib.FullVersion>();
const directVersion = await getFactorioVersion(factorioDir);
if (directVersion !== null) {
versions.add(directVersion);
return { direct: true, versions };
}
for (const name of await fs.readdir(factorioDir)) {
// We do not use withFileTypes or lstat because we want to follow symlinks
const fullPath = path.join(factorioDir, name);
try {
const stats = await fs.stat(fullPath);
if (!stats.isDirectory()) {
continue;
}
} catch {
continue;
}
const version = await getFactorioVersion(fullPath);
if (version === null) {
continue;
}
versions.add(version);
}
return { direct: false, versions };
}
/**
* Downloads a ZIP file from a URL using native fetch and extracts it
*
* @param url - The URL of the ZIP file
* @param targetDir - Directory to extract the ZIP into
* @throws Network error if status of request is not ok
*/
async function downloadAndExtractZip(url: string, targetDir: string) {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
}
// Load the ZIP with JSZip
const buffer = Buffer.from(await res.arrayBuffer());
const zip = await JSZip.loadAsync(buffer);
// Extract each file/folder
await fs.ensureDir(targetDir);
await Promise.all(
Object.values(zip.files).map(async (entry) => {
const parts = entry.name.split("/").filter(Boolean);
const strippedPath = parts.slice(1).join("/");
if (!strippedPath) {
return; // This copies the behaviour of tar --strip-components 1
}
const entryPath = path.join(targetDir, strippedPath);
if (entry.dir) {
await fs.ensureDir(entryPath);
} else {
await fs.ensureDir(path.dirname(entryPath));
await fs.writeFile(entryPath, await entry.async("nodebuffer"));
}
})
);
}
/**
* Downloads a tar.gz file from a URL using native fetch and extracts it
*
* @param url - The URL of the ZIP file
* @param targetDir - Directory to extract the ZIP into
* @throws Network error if status of request is not ok
*/
async function downloadAndExtractTar(url: string, targetDir: string) {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
}
// Write the buffer to a temp file
const tmpFilePath = `${targetDir}.tmp`;
const buffer = Buffer.from(await res.arrayBuffer());
await fs.writeFile(tmpFilePath, buffer);
await fs.ensureDir(targetDir);
// Execute tar CLI to extract
try {
await execFileAsync("tar", ["-xf", tmpFilePath, "-C", targetDir, "--strip-components", "1"]);
} catch (err: any) {
if (err.code === "ENOENT") {
throw new Error("error executing tar command, do you have 'xz-utils' installed?");
}
throw err;
} finally {
// Clean up temp file
await fs.remove(tmpFilePath);
}
}
/**
* Find factorio data directory of the given version
*
* Searches the given factorio dir for an installation of Factorio of
* the target version.
*
* @param factorioDir - Path to Factorio installation dir(s).
* @param factorioDir - Path to Factorio installation dirs.
* @param targetVersion -
* Version to look for, supports the special value "latest" for the
* latest version available.
@@ -80,40 +192,39 @@ function versionOrder(a: string, b: string) {
* @internal
*/
async function findVersion(factorioDir: string, targetVersion: lib.TargetVersion): Promise<[string, lib.FullVersion]> {
// There are two supported setups: having the factorio dir be the actual
// install directory, and having the factorio dir be a folder containing
// multiple install directories
let simpleVersion = await getVersion(path.join(factorioDir, "data", "changelog.txt"));
if (simpleVersion !== null) {
if (simpleVersion === targetVersion || simpleVersion.startsWith(targetVersion) || targetVersion === "latest") {
if (lib.isFullVersion(simpleVersion)) {
return [path.join(factorioDir, "data"), simpleVersion];
}
// Check if this is a direct installation dir
const directVersion = await getFactorioVersion(factorioDir);
if (directVersion !== null) {
if (targetVersion === "latest" || directVersion === targetVersion || directVersion.startsWith(targetVersion)) {
return [path.join(factorioDir, "data"), directVersion];
}
throw new Error(
`Factorio version ${targetVersion} was requested, but install directory contains ${simpleVersion}`
);
throw new Error(`Unable to find Factorio version ${targetVersion}`);
}
let versions = new Map<lib.FullVersion, string>();
for (let entry of await fs.readdir(factorioDir, { withFileTypes: true })) {
if (!entry.isDirectory()) {
// Otherwise assume it is a directory of multiple versions
const versions = new Map<lib.FullVersion, string>();
for (const name of await fs.readdir(factorioDir)) {
// We do not use withFileTypes or lstat because we want to follow symlinks
const fullPath = path.join(factorioDir, name);
try {
const stats = await fs.stat(fullPath);
if (!stats.isDirectory()) {
continue;
}
} catch {
continue;
}
let version = await getVersion(path.join(factorioDir, entry.name, "data", "changelog.txt"));
if (version === null || !lib.isFullVersion(version)) {
const version = await getFactorioVersion(fullPath);
if (version === null) {
continue;
}
if (version === targetVersion) {
return [path.join(factorioDir, entry.name, "data"), version];
return [path.join(factorioDir, name, "data"), version];
}
versions.set(version, entry.name);
versions.set(version, name);
}
if (!versions.size) {
@@ -871,6 +982,37 @@ export class FactorioServer extends events.EventEmitter<FactorioServerEvents> {
this._state = "init";
}
/**
* Check of updated versions to download
*
* Should be called before init for the new version to be detected during init
*/
async checkForUpdates(availableVersions: lib.ExternalFactorioVersion[]) {
const targetVersion = this._targetVersion;
const latestVersion = availableVersions
.filter(v => (
targetVersion === "latest" || v.version === targetVersion || v.version.startsWith(targetVersion)
))
.sort((v1, v2) => versionOrder(v1.version, v2.version))
[0];
const installedVersions = await listFactorioVersions(this._factorioDir);
if (!installedVersions.versions.has(latestVersion.version)) {
const v = latestVersion.version;
// Can't download if not linux or this is a direct install
if (installedVersions.direct || process.platform !== "linux") {
this._logger.info(`A newer version of factorio is available (${v}) but must be manually downloaded`);
return;
}
// Download the newer version
this._logger.info(`A newer version of factorio is available (${v}), starting download...`);
const dst = path.join(this._factorioDir, latestVersion.version);
await downloadAndExtractTar(latestVersion.headlessUrl, dst);
this._logger.info(`Downloaded factorio version ${v}`);
}
}
/**
* The version of Factorio in use. This will be the actual version if
* "latest" (the default) was specified as `factorioVersion` to the constructor.
@@ -1421,9 +1563,12 @@ export class FactorioServer extends events.EventEmitter<FactorioServerEvents> {
// For testing only
export const _getVersion = getVersion;
export const _getFactorioVersion = getFactorioVersion;
export const _versionOrder = versionOrder;
export const _findVersion = findVersion;
export const _listFactorioVersions = listFactorioVersions;
export const _downloadAndExtractZip = downloadAndExtractZip;
export const _downloadAndExtractTar = downloadAndExtractTar;
export const _randomDynamicPort = randomDynamicPort;
export const _generatePassword = generatePassword;
export const _parseOutput = parseOutput;
+1 -1
View File
@@ -402,7 +402,7 @@ export class DebugWsMessageEvent {
export class FactorioVersionsRequest {
declare ["constructor"]: typeof FactorioVersionsRequest;
static type = "request" as const;
static src = "control" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "core.external.get_factorio_versions" as const;
+1 -1
View File
@@ -60,7 +60,7 @@ function parseFactorioVersions(html: string): ExternalFactorioVersion[] {
versions.set(version, {
version: version,
stable: kind === "stable",
headlessUrl: `www.factorio.com/get-download/${version}/headless/linux64`,
headlessUrl: `https://www.factorio.com/get-download/${version}/headless/linux64`,
});
}
}
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,4 @@
; Test data for exportLocale
[test]
base-a=1
base-b=2
@@ -0,0 +1,3 @@
---------------------------------------------------------------------------------------------------
Version: 0.1.2
About: Dummy changelog.txt for FactorioServer tests
@@ -0,0 +1,4 @@
; Test data for exportLocale
[test]
core-a=1
core-b=2
View File
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,4 @@
; Test data for exportLocale
[test]
base-a=1
base-b=2
@@ -0,0 +1,3 @@
---------------------------------------------------------------------------------------------------
Version: 0.1
About: Dummy changelog.txt for FactorioServer tests
@@ -0,0 +1,4 @@
; Test data for exportLocale
[test]
core-a=1
core-b=2
+425 -43
View File
@@ -8,20 +8,21 @@ const hostServer = require("@clusterio/host/dist/node/src/server");
const lib = require("@clusterio/lib");
const { wait } = lib;
const { testLines } = require("../lib/factorio/lines");
const { slowTest } = require("../integration");
describe("host/server", function() {
describe("_getVersion()", function() {
describe("_getFactorioVersion()", function() {
it("should get the version from a changelog", async function() {
let version = await hostServer._getVersion(path.join("test", "file", "changelog-test.txt"));
let version = await hostServer._getFactorioVersion(path.join("test", "file", "changelogs", "good"));
assert.equal(version, "0.1.1");
});
it("should return null if unable to find the version", async function() {
let version = await hostServer._getVersion(path.join("test", "file", "changelog-bad.txt"));
let version = await hostServer._getFactorioVersion(path.join("test", "file", "changelogs", "bad"));
assert.equal(version, null);
});
it("should return null if file does not exist", async function() {
let version = await hostServer._getVersion(path.join("test", "file", "does-not-exist.txt"));
let version = await hostServer._getFactorioVersion(path.join("test", "file", "changelogs", "not-exists"));
assert.equal(version, null);
});
});
@@ -38,50 +39,131 @@ describe("host/server", function() {
});
describe("_findVersion()", function() {
it("should find a given install dir with latest as target", async function() {
let installDir = path.join("test", "file", "factorio");
let [dir, version] = await hostServer._findVersion(installDir, "latest");
assert.equal(dir, path.join(installDir, "data"));
assert.equal(version, "0.1.1");
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");
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");
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");
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"),
new Error("Unable to find Factorio version 0.1.2")
);
});
});
it("should find a given install dir with correct version as target", async function() {
let installDir = path.join("test", "file", "factorio");
let [dir, version] = await hostServer._findVersion(installDir, "0.1.1");
assert.equal(dir, path.join(installDir, "data"));
assert.equal(version, "0.1.1");
describe("mutli install", 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"),
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");
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");
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"),
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"),
new Error(`Unable to find any Factorio install in ${installDir}`)
);
});
});
it("should reject if the install dir version does not match target version", async function() {
let installDir = path.join("test", "file", "factorio");
await assert.rejects(
hostServer._findVersion(installDir, "0.1.2"),
new Error("Factorio version 0.1.2 was requested, but install directory contains 0.1.1")
);
});
describe("_listFactorioVersions()", function() {
it("should list the version in a direct install", async function() {
const installDir = path.join("test", "file", "factorio", "0.1.1");
const installedVersions = await hostServer._listFactorioVersions(installDir);
assert.deepEqual(installedVersions, {
direct: true,
versions: new Set(["0.1.1"]),
});
});
it("should search given directory for latest Factorio install", async function() {
let installDir = path.join("test", "file");
let [dir, version] = await hostServer._findVersion(installDir, "latest");
assert.equal(dir, path.join(installDir, "factorio", "data"));
assert.equal(version, "0.1.1");
it("should list all versions in a directory", async function() {
const installDir = path.join("test", "file", "factorio");
const installedVersions = await hostServer._listFactorioVersions(installDir);
assert.deepEqual(installedVersions, {
direct: false,
versions: new Set(["0.1.1", "0.1.2"]),
});
});
it("should search given directory for given Factorio install", async function() {
let installDir = path.join("test", "file");
let [dir, version] = await hostServer._findVersion(installDir, "0.1.1");
assert.equal(dir, path.join(installDir, "factorio", "data"));
assert.equal(version, "0.1.1");
});
describe("downloadAndExtractZip", function() {
let _fetch;
beforeEach(function() {
_fetch = global.fetch;
});
it("should reject if no factorio install with the given version was found", async function() {
let installDir = path.join("test", "file");
await assert.rejects(
hostServer._findVersion(installDir, "0.1.2"),
new Error("Unable to find Factorio version 0.1.2")
);
afterEach(function() {
global.fetch = _fetch;
});
it("should reject if no factorio install was found", async function() {
let installDir = path.join("test", "file", "instances");
await assert.rejects(
hostServer._findVersion(installDir, "latest"),
new Error(`Unable to find any Factorio install in ${installDir}`)
);
it("works", async function() {
slowTest(this);
const url = "https://github.com/clusterio/clusterio/archive/refs/tags/v2.0.0-alpha.22.zip";
const downloads = path.join("temp", "test", "downloads");
await fs.emptyDir(downloads);
await hostServer._downloadAndExtractZip(url, path.join(downloads, "zip"));
assert.ok(await fs.exists(path.join(downloads, "zip", "packages", "controller", "package.json")));
});
it("errors and bad status", async function() {
global.fetch = () => ({ ok: false, status: -1, statusText: "Fetch called" });
await assert.rejects(hostServer._downloadAndExtractZip("url does not matter"), /-1 Fetch called/);
});
});
describe("downloadAndExtractTar", function() {
let _fetch;
beforeEach(function() {
_fetch = global.fetch;
});
afterEach(function() {
global.fetch = _fetch;
});
it("works", async function() {
const url = "https://github.com/clusterio/clusterio/archive/refs/tags/v2.0.0-alpha.22.tar.gz";
const downloads = path.join("temp", "test", "downloads");
await fs.emptyDir(downloads);
await hostServer._downloadAndExtractTar(url, path.join(downloads, "tar"));
assert.ok(await fs.exists(path.join(downloads, "tar", "packages", "controller", "package.json")));
});
it("errors and bad status", async function() {
global.fetch = () => ({ ok: false, status: -1, statusText: "Fetch called" });
await assert.rejects(hostServer._downloadAndExtractTar("url does not matter"), /-1 Fetch called/);
});
});
@@ -157,7 +239,7 @@ describe("host/server", function() {
describe(".version", function() {
it("should return the version detected", function() {
assert.equal(server.version, "0.1.1");
assert.equal(server.version, "0.1.2");
});
});
@@ -235,5 +317,305 @@ describe("host/server", function() {
await wait(21); // Wait until after shutdown timeout
});
});
describe(".checkForUpdates()", function() {
let _fetch;
let fetchCalledWith;
let _platform = process.platform;
beforeEach(function() {
_fetch = global.fetch;
fetchCalledWith = null;
global.fetch = async function(url) {
fetchCalledWith = url;
return {
ok: false,
status: -1,
statusText: "Fetch called",
};
};
});
afterEach(function() {
global.fetch = _fetch;
server._factorioDir = path.join("test", "file", "factorio");
Object.defineProperty(process, "platform", {
value: _platform,
});
});
describe("full version", function() {
it("should do nothing when there is no newer version", async function() {
server._factorioDir = path.join("test", "file", "factorio");
server._targetVersion = "0.1.1";
await server.checkForUpdates([{
stable: true,
version: "0.1.1",
headlessUrl: "test1",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test2",
}]);
assert.equal(fetchCalledWith, null);
});
it("should do nothing for direct installs", async function() {
server._factorioDir = path.join("test", "file", "factorio", "0.1.1");
server._targetVersion = "0.1.5";
await server.checkForUpdates([{
stable: true,
version: "0.1.5",
headlessUrl: "test1",
}, {
stable: true,
version: "0.1.1",
headlessUrl: "test1",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test2",
}]);
assert.equal(fetchCalledWith, null);
});
it("should do nothing when on windows", async function() {
let logLine = null;
server._logger = { info: line => { logLine = line; } };
Object.defineProperty(process, "platform", { value: "win32" });
server._factorioDir = path.join("test", "file", "factorio");
server._targetVersion = "0.1.5";
await server.checkForUpdates([{
stable: true,
version: "0.1.5",
headlessUrl: "test1",
}, {
stable: true,
version: "0.1.1",
headlessUrl: "test2",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test3",
}]);
assert.equal(fetchCalledWith, null);
assert.ok(logLine !== null);
assert.ok(logLine.endsWith("but must be manually downloaded"));
});
it("should do attempt to download on linux", async function() {
let logLine = null;
server._logger = { info: line => { logLine = line; } };
Object.defineProperty(process, "platform", { value: "linux" });
server._factorioDir = path.join("test", "file", "factorio");
server._targetVersion = "0.1.5";
await assert.rejects(server.checkForUpdates([{
stable: true,
version: "0.1.5",
headlessUrl: "test1",
}, {
stable: true,
version: "0.1.1",
headlessUrl: "test2",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test3",
}]), new Error("Failed to fetch test1: -1 Fetch called"));
assert.equal(fetchCalledWith, "test1");
assert.ok(logLine !== null);
assert.ok(logLine.endsWith("starting download..."));
});
});
describe("partial version", function() {
it("should do nothing when there is no newer version", async function() {
server._factorioDir = path.join("test", "file", "factorio");
server._targetVersion = "0.1";
await server.checkForUpdates([{
stable: true,
version: "0.1.1",
headlessUrl: "test1",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test2",
}]);
assert.equal(fetchCalledWith, null);
});
it("should do nothing for direct installs", async function() {
server._factorioDir = path.join("test", "file", "factorio", "0.1.1");
server._targetVersion = "0.1";
await server.checkForUpdates([{
stable: true,
version: "0.1.5",
headlessUrl: "test1",
}, {
stable: true,
version: "0.1.1",
headlessUrl: "test1",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test2",
}]);
assert.equal(fetchCalledWith, null);
});
it("should do nothing when on windows", async function() {
let logLine = null;
server._logger = { info: line => { logLine = line; } };
Object.defineProperty(process, "platform", { value: "win32" });
server._factorioDir = path.join("test", "file", "factorio");
server._targetVersion = "0.1";
await server.checkForUpdates([{
stable: true,
version: "0.1.5",
headlessUrl: "test1",
}, {
stable: true,
version: "0.1.1",
headlessUrl: "test2",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test3",
}]);
assert.equal(fetchCalledWith, null);
assert.ok(logLine !== null);
assert.ok(logLine.endsWith("but must be manually downloaded"));
});
it("should do attempt to download on linux", async function() {
let logLine = null;
server._logger = { info: line => { logLine = line; } };
Object.defineProperty(process, "platform", { value: "linux" });
server._factorioDir = path.join("test", "file", "factorio");
server._targetVersion = "0.1";
await assert.rejects(server.checkForUpdates([{
stable: true,
version: "0.1.5",
headlessUrl: "test1",
}, {
stable: true,
version: "0.1.1",
headlessUrl: "test2",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test3",
}]), new Error("Failed to fetch test1: -1 Fetch called"));
assert.equal(fetchCalledWith, "test1");
assert.ok(logLine !== null);
assert.ok(logLine.endsWith("starting download..."));
});
});
describe("latest version", function() {
it("should do nothing when there is no newer version", async function() {
server._factorioDir = path.join("test", "file", "factorio");
server._targetVersion = "latest";
await server.checkForUpdates([{
stable: true,
version: "0.1.1",
headlessUrl: "test1",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test2",
}]);
assert.equal(fetchCalledWith, null);
});
it("should do nothing for direct installs", async function() {
server._factorioDir = path.join("test", "file", "factorio", "0.1.1");
server._targetVersion = "latest";
await server.checkForUpdates([{
stable: true,
version: "0.1.5",
headlessUrl: "test1",
}, {
stable: true,
version: "0.1.1",
headlessUrl: "test1",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test2",
}]);
assert.equal(fetchCalledWith, null);
});
it("should do nothing when on windows", async function() {
let logLine = null;
server._logger = { info: line => { logLine = line; } };
Object.defineProperty(process, "platform", { value: "win32" });
server._factorioDir = path.join("test", "file", "factorio");
server._targetVersion = "latest";
await server.checkForUpdates([{
stable: true,
version: "0.1.5",
headlessUrl: "test1",
}, {
stable: true,
version: "0.1.1",
headlessUrl: "test2",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test3",
}]);
assert.equal(fetchCalledWith, null);
assert.ok(logLine !== null);
assert.ok(logLine.endsWith("but must be manually downloaded"));
});
it("should do attempt to download on linux", async function() {
let logLine = null;
server._logger = { info: line => { logLine = line; } };
Object.defineProperty(process, "platform", { value: "linux" });
server._factorioDir = path.join("test", "file", "factorio");
server._targetVersion = "latest";
await assert.rejects(server.checkForUpdates([{
stable: true,
version: "0.1.5",
headlessUrl: "test1",
}, {
stable: true,
version: "0.1.1",
headlessUrl: "test2",
}, {
stable: false,
version: "0.1.0",
headlessUrl: "test3",
}]), new Error("Failed to fetch test1: -1 Fetch called"));
assert.equal(fetchCalledWith, "test1");
assert.ok(logLine !== null);
assert.ok(logLine.endsWith("starting download..."));
});
});
it("should download a version correctly (live api)", async function() {
slowTest(this);
if (_platform !== "linux") {
this.skip();
}
server._factorioDir = path.join("test", "file", "factorioDownload");
server._targetVersion = "latest";
global.fetch = _fetch;
await fs.emptyDir(server._factorioDir);
await server.checkForUpdates([{
stable: true,
version: "2.0.73",
headlessUrl: "https://www.factorio.com/get-download/2.0.73/headless/linux64",
}]);
});
});
});
});
+4 -4
View File
@@ -5,14 +5,14 @@ const fs = require("fs-extra");
const path = require("path");
const lib = require("@clusterio/lib");
const { FactorioServer, _getVersion } = require("@clusterio/host/dist/node/src/server");
const { FactorioServer, _getFactorioVersion } = require("@clusterio/host/dist/node/src/server");
const { logger } = lib;
const { slowTest, factorioDir } = require("./index");
describe("Integration of host/src/server", function() {
describe("_getVersion()", function() {
describe("getFactorioVersion()", function() {
it("should get a version from factorio's changelog.txt", async function() {
function checkVersion(version) {
if (!/^\d+\.\d+\.\d+$/.test(version)) {
@@ -20,7 +20,7 @@ describe("Integration of host/src/server", function() {
}
}
let version = await _getVersion(path.join(factorioDir, "data", "changelog.txt"));
let version = await _getFactorioVersion(factorioDir);
if (version !== null) {
checkVersion(version);
} else {
@@ -30,7 +30,7 @@ describe("Integration of host/src/server", function() {
}
checkVersion(
await _getVersion(path.join(factorioDir, entry.name, "data", "changelog.txt"))
await _getFactorioVersion(path.join(factorioDir, entry.name))
);
}
}
+3 -3
View File
@@ -54,12 +54,12 @@ describe("FactorioVersions", function() {
{
stable: false,
version: "2.0.73",
headlessUrl: "www.factorio.com/get-download/2.0.73/headless/linux64",
headlessUrl: "https://www.factorio.com/get-download/2.0.73/headless/linux64",
},
{
stable: true,
version: "2.0.72",
headlessUrl: "www.factorio.com/get-download/2.0.72/headless/linux64",
headlessUrl: "https://www.factorio.com/get-download/2.0.72/headless/linux64",
},
]);
});
@@ -90,7 +90,7 @@ describe("FactorioVersions", function() {
{
stable: true,
version: "2.0.73",
headlessUrl: "www.factorio.com/get-download/2.0.73/headless/linux64",
headlessUrl: "https://www.factorio.com/get-download/2.0.73/headless/linux64",
},
]);
});