mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Merge pull request #831 from Cooldude2606/feature/automatic-factorio-download
Add automatic headless download and update on linux
This commit is contained in:
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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
@@ -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
@@ -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.
File renamed without changes.
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
|
||||
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
@@ -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",
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -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",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user