diff --git a/packages/controller/src/BaseConnection.ts b/packages/controller/src/BaseConnection.ts index 19cc47a6..76949eb8 100644 --- a/packages/controller/src/BaseConnection.ts +++ b/packages/controller/src/BaseConnection.ts @@ -80,7 +80,7 @@ export default class BaseConnection extends lib.Link { return modPack; } - getMod(mod: { name: string, version: lib.GameVersion, sha1?: string }) { + getMod(mod: { name: string, version: lib.SourceVersion, sha1?: string }) { let filename = lib.ModInfo.filename(mod.name, mod.version); let modInfo = this._controller.modStore.files.get(filename); if (!modInfo) { diff --git a/packages/controller/src/ControlConnection.ts b/packages/controller/src/ControlConnection.ts index 12e46e77..293f28d0 100644 --- a/packages/controller/src/ControlConnection.ts +++ b/packages/controller/src/ControlConnection.ts @@ -707,7 +707,7 @@ export default class ControlConnection extends BaseConnection { const release = releases.releases .filter(info => versionRange.testVersion(info.version)) .reduce((max, cur) => ( - max && lib.integerGameVersion(max.version) > lib.integerGameVersion(cur.version) ? max : cur + max && lib.integerSourceVersion(max.version) > lib.integerSourceVersion(cur.version) ? max : cur ), undefined); if (!release) { diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index 3b72dcca..8051a0cc 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -766,7 +766,7 @@ export default class Controller { // migrate: 2.0.0-alpha.22 - old buildins used X.Y rather than X.Y.Z // All other versions are kept verbatim so they still match the mod's file name. if (lib.isPartialVersion(mod.version)) { - mod.version = lib.normaliseFullVersion(mod.version) as lib.GameVersion; + mod.version = lib.normaliseFullVersion(mod.version) as lib.SourceVersion; } } return json; diff --git a/packages/ctl/src/commands_mod.ts b/packages/ctl/src/commands_mod.ts index 169b8fb5..83f8873a 100644 --- a/packages/ctl/src/commands_mod.ts +++ b/packages/ctl/src/commands_mod.ts @@ -20,7 +20,7 @@ modCommands.add(new lib.Command({ yargs.positional("mod-version", { describe: "Version of the mod", type: "string" }); }], handler: async function(args: { name: string, modVersion: string }, control: Control) { - if (!lib.isGameVersion(args.modVersion)) { + if (!lib.isSourceVersion(args.modVersion)) { throw new lib.CommandError("mod-version must match format digit.digit.digit"); } let modInfo = await control.send(new lib.ModGetRequest(args.name, args.modVersion)); @@ -108,7 +108,7 @@ modCommands.add(new lib.Command({ }, control: Control ) { - const searchVersion = lib.normaliseGameVersion(String(args.factorioVersion)); + const searchVersion = lib.normaliseSourceVersion(String(args.factorioVersion)); if (searchVersion === undefined) { throw new Error(`Invalid factorio version "${args.factorioVersion}"`); } @@ -191,7 +191,7 @@ modCommands.add(new lib.Command({ yargs.positional("mod-version", { describe: "Version of mod to download", type: "string" }); }], handler: async function(args: { name: string, modVersion: string }, control: Control) { - if (!lib.isGameVersion(args.modVersion)) { + if (!lib.isSourceVersion(args.modVersion)) { throw new lib.CommandError("mod-version must match format digit.digit.digit"); } let streamId = await control.send(new lib.ModDownloadRequest(args.name, args.modVersion)); @@ -211,7 +211,7 @@ modCommands.add(new lib.Command({ yargs.positional("mod-version", { describe: "Version of mod to delete", type: "string" }); }], handler: async function(args: { name: string, modVersion: string }, control: Control) { - if (!lib.isGameVersion(args.modVersion)) { + if (!lib.isSourceVersion(args.modVersion)) { throw new lib.CommandError("mod-version must match format digit.digit.digit"); } await control.send(new lib.ModDeleteRequest(args.name, args.modVersion)); diff --git a/packages/ctl/src/commands_mod_pack.ts b/packages/ctl/src/commands_mod_pack.ts index 6d3fdfa1..a4af8a90 100644 --- a/packages/ctl/src/commands_mod_pack.ts +++ b/packages/ctl/src/commands_mod_pack.ts @@ -132,7 +132,7 @@ function setModPackMods(modPack: lib.ModPack, mods: string[] | undefined) { if (!version) { throw new lib.CommandError("Added mod must be formatted as name:version or name:version:sha1"); } - if (!lib.isGameVersion(version)) { + if (!lib.isSourceVersion(version)) { throw new lib.CommandError("version must match the format digit.digit.digit"); } if (sha1 && !/^[0-9a-f]{40}$/.test(sha1)) { diff --git a/packages/lib/src/ModStore.ts b/packages/lib/src/ModStore.ts index 3192ec55..625ae253 100644 --- a/packages/lib/src/ModStore.ts +++ b/packages/lib/src/ModStore.ts @@ -5,7 +5,7 @@ import { Static } from "@sinclair/typebox"; import { logger } from "./logging"; import { downloadFile, safeOutputFile } from "./file_ops"; import { ModPortalReleaseSchema, ModPortalDetailsSchema, ModNameVersionPair } from "./data/messages_mod"; -import { ModInfo, ModPack, MajorMinorVersion, ModVersionEquality, GameVersion } from "./data"; +import { ModInfo, ModPack, MajorMinorVersion, ModVersionEquality, SourceVersion } from "./data"; export interface ModStoreEvents { /** A stored mod was created, updated or deleted */ @@ -44,7 +44,7 @@ export default class ModStore extends events.EventEmitter { super(); } - getMod(name: string, version: GameVersion, sha1?: string) { + getMod(name: string, version: SourceVersion, sha1?: string) { const mod = this.files.get(ModInfo.filename(name, version)); if (!mod || sha1 && sha1 !== mod.sha1) { return undefined; @@ -86,11 +86,11 @@ export default class ModStore extends events.EventEmitter { this.emit("change", modInfo); } - async loadMod(name: string, version: GameVersion) { + async loadMod(name: string, version: SourceVersion) { return await this.loadFile(ModInfo.filename(name, version)); } - async deleteMod(name: string, version: GameVersion) { + async deleteMod(name: string, version: SourceVersion) { return await this.deleteFile(ModInfo.filename(name, version)); } diff --git a/packages/lib/src/data/ModInfo.ts b/packages/lib/src/data/ModInfo.ts index f5ca2fa3..14416f54 100644 --- a/packages/lib/src/data/ModInfo.ts +++ b/packages/lib/src/data/ModInfo.ts @@ -9,7 +9,7 @@ import { ModRecord } from "./ModPack"; import { MajorMinorVersion, MajorMinorVersionSchema, normaliseMajorMinorVersion, integerMajorMinorVersion, - GameVersion, GameVersionSchema, isGameVersion, normaliseGameVersion, integerGameVersion, + SourceVersion, SourceVersionSchema, isSourceVersion, normaliseSourceVersion, integerSourceVersion, ModVersionEquality, } from "./version"; @@ -133,18 +133,18 @@ export default class ModInfo { * it is only safe to use for building file names and identifiers; use * {@link integerVersion} to compare it. */ - version = "0.0.0" as GameVersion; + version = "0.0.0" as SourceVersion; /** * Integer representation of the version, normalised the lenient way the * game reads it so it can be compared and sorted. */ get integerVersion() { - return integerGameVersion(this.version); + return integerSourceVersion(this.version); } get normalisedVersion() { - return normaliseGameVersion(this.version); + return normaliseSourceVersion(this.version); } /** @@ -227,7 +227,7 @@ export default class ModInfo { * @param version - Mod's version * @returns string containing {name}_{version}.zip */ - static filename(name: string, version: GameVersion) { + static filename(name: string, version: SourceVersion) { return `${name}_${version}.zip`; } @@ -258,13 +258,13 @@ export default class ModInfo { // lenient format the game accepts; they are normalised when read in fromJSON. static infoJsonSchema = Type.Object({ "name": Type.String(), - "version": GameVersionSchema, + "version": SourceVersionSchema, "title": Type.String(), "author": Type.String(), "contact": Type.Optional(Type.String()), "homepage": Type.Optional(Type.String()), "description": Type.Optional(Type.String()), - "factorio_version": Type.Optional(GameVersionSchema), + "factorio_version": Type.Optional(SourceVersionSchema), "dependencies": Type.Optional(Type.Array(Type.String())), }); @@ -287,7 +287,7 @@ export default class ModInfo { // info.json fields if (json.name) { modInfo.name = json.name; } if (json.version) { - if (!isGameVersion(json.version)) { + if (!isSourceVersion(json.version)) { throw new Error(`Invalid mod version "${json.version}"`); } modInfo.version = json.version; @@ -298,7 +298,7 @@ export default class ModInfo { if (json.homepage) { modInfo.homepage = json.homepage; } if (json.description) { modInfo.description = json.description; } if (json.factorio_version) { - const factorioVersion = normaliseGameVersion(json.factorio_version); + const factorioVersion = normaliseSourceVersion(json.factorio_version); if (factorioVersion === undefined) { throw new Error(`Invalid factorio_version "${json.factorio_version}"`); } @@ -332,7 +332,7 @@ export default class ModInfo { if (this.contact) { json.contact = this.contact; } if (this.homepage) { json.homepage = this.homepage; } if (this.description) { json.description = this.description; } - if (this.factorioVersion !== "0.12") { json.factorio_version = this.factorioVersion as GameVersion; } + if (this.factorioVersion !== "0.12") { json.factorio_version = this.factorioVersion as SourceVersion; } if (this.dependencies.length !== 1 || this.dependencies[0].name !== "base") { json.dependencies = this.dependencySpecifications; } diff --git a/packages/lib/src/data/ModPack.ts b/packages/lib/src/data/ModPack.ts index 6e77644f..4968980d 100644 --- a/packages/lib/src/data/ModPack.ts +++ b/packages/lib/src/data/ModPack.ts @@ -12,7 +12,7 @@ import ModInfo, { ModDependencyUnsatisfiedReason } from "./ModInfo"; import { PartialVersion, PartialVersionSchema, integerPartialVersion, - GameVersion, GameVersionSchema, normaliseFullVersion, + SourceVersion, SourceVersionSchema, normaliseFullVersion, } from "./version"; @@ -47,7 +47,7 @@ export interface ModRecord { /** if mod is to be loaded. */ enabled: boolean, /** version of the mod, verbatim as it appears in the mod's file name. */ - version: GameVersion, + version: SourceVersion, /** SHA1 hash of the zip file. */ sha1?: string, /** Used inside packages\web_ui\src\components\ModPackViewPage.tsx to define an error type. */ @@ -61,7 +61,7 @@ export interface ModRecord { const ModRecordJsonSchema = Type.Object({ "name": Type.String(), "enabled": Type.Boolean(), - "version": GameVersionSchema, + "version": SourceVersionSchema, "sha1": Type.Optional(Type.String()), }); @@ -434,7 +434,7 @@ export default class ModPack { * @return Built in mods for the given version */ static getBuiltinMods(factorioVersion: PartialVersion) { - const version = normaliseFullVersion(factorioVersion) as GameVersion; + const version = normaliseFullVersion(factorioVersion) as SourceVersion; const integerVersion = integerPartialVersion(factorioVersion); let defaultMods: ModRecord[] = [ // "core" not included because core cannot be disabled diff --git a/packages/lib/src/data/messages_mod.ts b/packages/lib/src/data/messages_mod.ts index 17def204..4d5a09e3 100644 --- a/packages/lib/src/data/messages_mod.ts +++ b/packages/lib/src/data/messages_mod.ts @@ -5,7 +5,7 @@ import { JsonString, jsonArray } from "./composites"; import { MajorMinorVersion, MajorMinorVersionSchema, normaliseMajorMinorVersion, - GameVersion, GameVersionSchema, + SourceVersion, SourceVersionSchema, ModVersionEquality, } from "./version"; @@ -119,13 +119,13 @@ export class ModGetRequest { constructor( public name: string, - public version: GameVersion, + public version: SourceVersion, public sha1?: string, ) { } static jsonSchema = Type.Object({ "name": Type.String(), - "version": GameVersionSchema, + "version": SourceVersionSchema, "sha1": Type.Optional(Type.String()), }); @@ -205,9 +205,9 @@ export class ModSearchRequest { // Define the structure for the latest release info from the portal export const ModPortalReleaseSchema = Type.Object({ - version: GameVersionSchema, + version: SourceVersionSchema, // Match the structure from ModStore's ModRelease/ModDetails - info_json: Type.Object({ factorio_version: GameVersionSchema }), + info_json: Type.Object({ factorio_version: SourceVersionSchema }), released_at: Type.String(), // ISO 8601 date string download_url: Type.String(), file_name: Type.String(), @@ -276,13 +276,13 @@ export class ModDownloadRequest { constructor( public name: string, - public version: GameVersion, + public version: SourceVersion, public sha1?: string, ) { } static jsonSchema = Type.Object({ "name": Type.String(), - "version": GameVersionSchema, + "version": SourceVersionSchema, "sha1": Type.Optional(Type.String()), }); @@ -302,12 +302,12 @@ export class ModDeleteRequest { constructor( public name: string, - public version: GameVersion, + public version: SourceVersion, ) { } static jsonSchema = Type.Object({ "name": Type.String(), - "version": GameVersionSchema, + "version": SourceVersionSchema, }); static fromJSON(json: Static) { diff --git a/packages/lib/src/data/version.ts b/packages/lib/src/data/version.ts index 0b17b3ba..5844fb94 100644 --- a/packages/lib/src/data/version.ts +++ b/packages/lib/src/data/version.ts @@ -36,20 +36,24 @@ export const ApiVersions = ["0.13", "0.14", "0.15", "0.16", "0.17", "0.18", "1.0 * optional, and any trailing content is ignored. The pattern is intentionally * not anchored at the end. */ -const gameVersionRegExp = /^\s*(\d+)\.\s*(\d+)(?:\.\s*(\d+))?/; +const sourceVersionRegExp = /^\s*(\d+)\.\s*(\d+)(?:\.\s*(\d+))?/; /** * A raw, un-normalised version string exactly as the game reads it, such as a * mod's version taken from info.json or a mod file name. */ -declare const gameVersionSymbol: unique symbol; -export type GameVersion = string & { [gameVersionSymbol]: void }; -export const GameVersionSchema = Type.Unsafe( - Type.String({ pattern: gameVersionRegExp.source }) +declare const sourceVersionSymbol: unique symbol; +export type SourceVersion = string & { [sourceVersionSymbol]: void }; +export const SourceVersionSchema = Type.Unsafe( + Type.String({ pattern: sourceVersionRegExp.source }) ); -export function isGameVersion(input: string): input is GameVersion { - return gameVersionRegExp.test(input); +export function isSourceVersion(input: string): input is SourceVersion { + return sourceVersionRegExp.test(input); +} + +export function integerSourceVersion(version: SourceVersion) { + return integerFullVersion(normaliseSourceVersion(version)); } /** @@ -60,21 +64,17 @@ export function isGameVersion(input: string): input is GameVersion { * @returns The normalised full version, or undefined if no major and minor * could be read. */ -export function normaliseGameVersion(input: GameVersion): FullVersion; -export function normaliseGameVersion(input: string): FullVersion | undefined; +export function normaliseSourceVersion(input: SourceVersion): FullVersion; +export function normaliseSourceVersion(input: string): FullVersion | undefined; -export function normaliseGameVersion(input: string | GameVersion): FullVersion | undefined { - const match = gameVersionRegExp.exec(input); +export function normaliseSourceVersion(input: string | SourceVersion): FullVersion | undefined { + const match = sourceVersionRegExp.exec(input); if (match === null) { return undefined; } return `${Number(match[1])}.${Number(match[2])}.${Number(match[3] ?? "0")}` as FullVersion; } -export function integerGameVersion(version: GameVersion) { - return integerFullVersion(normaliseGameVersion(version)); -} - /** * Matches valid mod versions, where all parts are specified. */ @@ -213,8 +213,8 @@ export class ModVersionEquality { } } - testVersion(version: GameVersion) { - return this.testIntegerVersion(integerGameVersion(version)); + testVersion(version: SourceVersion) { + return this.testIntegerVersion(integerSourceVersion(version)); } toString() { @@ -276,11 +276,11 @@ export class ModVersionRange { return this.minVersion.testIntegerVersion(other) && this.maxVersion.testIntegerVersion(other); } - testVersion(version: PartialVersion | GameVersion) { + testVersion(version: PartialVersion | SourceVersion) { if (isPartialVersion(version)) { return this.testIntegerVersion(integerPartialVersion(version)); } - return this.testIntegerVersion(integerGameVersion(version)); + return this.testIntegerVersion(integerSourceVersion(version)); } combineVersion(other: ModVersionEquality) { diff --git a/packages/web_ui/src/components/ModsPage.tsx b/packages/web_ui/src/components/ModsPage.tsx index 7d2e8e92..8d76cc78 100644 --- a/packages/web_ui/src/components/ModsPage.tsx +++ b/packages/web_ui/src/components/ModsPage.tsx @@ -164,13 +164,16 @@ function SearchModsButton() { const handleControllerDownload = ( modName: string, modTitle: string | undefined, - modVersion: lib.GameVersion | undefined, + modVersion: lib.SourceVersion | undefined, portalFactorioVersion: lib.MajorMinorVersion, ) => { if (modVersion) { control.send( new lib.ModPortalDownloadRequest( - [{ name: modName, version: new lib.ModVersionEquality("=", lib.normaliseGameVersion(modVersion))}], + [{ + name: modName, + version: new lib.ModVersionEquality("=", lib.normaliseSourceVersion(modVersion)), + }], portalFactorioVersion ) ).then(() => { diff --git a/test/lib/data/version.test.js b/test/lib/data/version.test.js index 433fb8f3..92bba3e9 100644 --- a/test/lib/data/version.test.js +++ b/test/lib/data/version.test.js @@ -17,25 +17,25 @@ describe("lib/data/version", function() { } }); }); - describe("normaliseGameVersion()", function() { + describe("normaliseSourceVersion()", function() { it("should normalise lenient version strings the way the game reads them", function() { // Major and minor required, patch optional and defaulting to 0. - assert.equal(lib.normaliseGameVersion("1.1"), "1.1.0"); - assert.equal(lib.normaliseGameVersion("1.1.50"), "1.1.50"); - assert.equal(lib.normaliseGameVersion("0.18"), "0.18.0"); + assert.equal(lib.normaliseSourceVersion("1.1"), "1.1.0"); + assert.equal(lib.normaliseSourceVersion("1.1.50"), "1.1.50"); + assert.equal(lib.normaliseSourceVersion("0.18"), "0.18.0"); // Leading whitespace and zeros are dropped. - assert.equal(lib.normaliseGameVersion(" 1.1"), "1.1.0"); - assert.equal(lib.normaliseGameVersion("01.02.03"), "1.2.3"); - assert.equal(lib.normaliseGameVersion("1. 1. 50"), "1.1.50"); + assert.equal(lib.normaliseSourceVersion(" 1.1"), "1.1.0"); + assert.equal(lib.normaliseSourceVersion("01.02.03"), "1.2.3"); + assert.equal(lib.normaliseSourceVersion("1. 1. 50"), "1.1.50"); // Anything after the patch (or a non-numeric patch) is ignored. - assert.equal(lib.normaliseGameVersion("1.1.50-beta"), "1.1.50"); - assert.equal(lib.normaliseGameVersion("1.1.x"), "1.1.0"); - assert.equal(lib.normaliseGameVersion("1.1.50.60"), "1.1.50"); + assert.equal(lib.normaliseSourceVersion("1.1.50-beta"), "1.1.50"); + assert.equal(lib.normaliseSourceVersion("1.1.x"), "1.1.0"); + assert.equal(lib.normaliseSourceVersion("1.1.50.60"), "1.1.50"); }); it("should return undefined when no major.minor can be read", function() { const invalid = ["1", "", "latest", "abc", ".1.1", "x.y"]; for (const test of invalid) { - assert.equal(lib.normaliseGameVersion(test), undefined, test); + assert.equal(lib.normaliseSourceVersion(test), undefined, test); } }); });