Maintain strict tagging of GameVersion

This commit is contained in:
Cooldude2606 committed 2026-07-02 00:08:09 +01:00
1 parent 8029ad3b2d
commit 2e27c4f499
8 files changed
+37 -45

No files matched your search

+3 -5
View File
@@ -763,12 +763,10 @@ export default class Controller {
const serialized = rawJson as Static<typeof lib.ModPack.jsonSchema>[];
return serialized.map(json => {
for (const mod of json.mods) {
// Older mod packs recorded builtins with a major.minor version; pad
// those to major.minor.patch. Raw versions the game reads leniently
// (e.g. with leading zeros) are kept verbatim so they still match the
// mod's file name.
// 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);
mod.version = lib.normaliseFullVersion(mod.version) as lib.GameVersion;
}
}
return json;
+3 -3
View File
@@ -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.isFullVersion(args.modVersion)) {
if (!lib.isGameVersion(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));
@@ -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.isFullVersion(args.modVersion)) {
if (!lib.isGameVersion(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.isFullVersion(args.modVersion)) {
if (!lib.isGameVersion(args.modVersion)) {
throw new lib.CommandError("mod-version must match format digit.digit.digit");
}
await control.send(new lib.ModDeleteRequest(args.name, args.modVersion));
+1 -1
View File
@@ -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.isFullVersion(version)) {
if (!lib.isGameVersion(version)) {
throw new lib.CommandError("version must match the format digit.digit.digit");
}
if (sha1 && !/^[0-9a-f]{40}$/.test(sha1)) {
+8 -6
View File
@@ -8,8 +8,8 @@ import { findRoot } from "../zip_ops";
import { ModRecord } from "./ModPack";
import {
MajorMinorVersion, normaliseMajorMinorVersion, integerMajorMinorVersion,
GameVersion, GameVersionSchema, normaliseGameVersion, integerGameVersion,
MajorMinorVersion, MajorMinorVersionSchema, normaliseMajorMinorVersion, integerMajorMinorVersion,
GameVersion, GameVersionSchema, isGameVersion, normaliseGameVersion, integerGameVersion,
ModVersionEquality,
} from "./version";
@@ -143,6 +143,10 @@ export default class ModInfo {
return integerGameVersion(this.version);
}
get normalisedVersion() {
return normaliseGameVersion(this.version);
}
/**
* Display name of the mod.
* Sourced from info.json.
@@ -283,9 +287,7 @@ export default class ModInfo {
// info.json fields
if (json.name) { modInfo.name = json.name; }
if (json.version) {
// Validate that the raw version can be read, but keep it verbatim so
// it still matches the mod's file name on disk and the portal.
if (normaliseGameVersion(json.version) === undefined) {
if (!isGameVersion(json.version)) {
throw new Error(`Invalid mod version "${json.version}"`);
}
modInfo.version = json.version;
@@ -330,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; }
if (this.factorioVersion !== "0.12") { json.factorio_version = this.factorioVersion as GameVersion; }
if (this.dependencies.length !== 1 || this.dependencies[0].name !== "base") {
json.dependencies = this.dependencySpecifications;
}
+7 -7
View File
@@ -434,24 +434,24 @@ export default class ModPack {
* @return Built in mods for the given version
*/
static getBuiltinMods(factorioVersion: PartialVersion) {
factorioVersion = normaliseFullVersion(factorioVersion);
const version = normaliseFullVersion(factorioVersion) as GameVersion;
const integerVersion = integerPartialVersion(factorioVersion);
let defaultMods: ModRecord[] = [
// "core" not included because core cannot be disabled
{ name: "base", enabled: true, version: factorioVersion },
{ name: "base", enabled: true, version: version },
];
const integerVersion = integerPartialVersion(factorioVersion);
if (integerVersion >= integerPartialVersion("1.2")) {
defaultMods = defaultMods.concat([
{ name: "elevated-rails", enabled: false, version: factorioVersion },
{ name: "quality", enabled: false, version: factorioVersion },
{ name: "space-age", enabled: false, version: factorioVersion },
{ name: "elevated-rails", enabled: false, version: version },
{ name: "quality", enabled: false, version: version },
{ name: "space-age", enabled: false, version: version },
]);
}
if (integerVersion >= integerPartialVersion("2.1")) {
defaultMods = defaultMods.concat([
{ name: "recycler", enabled: false, version: factorioVersion },
{ name: "recycler", enabled: false, version: version },
]);
}
+13 -21
View File
@@ -37,16 +37,13 @@ export const ApiVersions = ["0.13", "0.14", "0.15", "0.16", "0.17", "0.18", "1.0
* not anchored at the end.
*/
const gameVersionRegExp = /^\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. It is deliberately a
* plain string alias so that any of the stricter, normalised version types
* ({@link FullVersion}, {@link MajorMinorVersion}, {@link PartialVersion}) widen
* into it for free, while a GameVersion can not be used where a normalised
* version is required without going through {@link normaliseGameVersion} or
* {@link integerGameVersion} first.
* mod's version taken from info.json or a mod file name.
*/
export type GameVersion = string;
declare const gameVersionSymbol: unique symbol;
export type GameVersion = string & { [gameVersionSymbol]: void };
export const GameVersionSchema = Type.Unsafe<GameVersion>(
Type.String({ pattern: gameVersionRegExp.source })
);
@@ -63,7 +60,10 @@ 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: string): FullVersion | undefined {
export function normaliseGameVersion(input: GameVersion): FullVersion;
export function normaliseGameVersion(input: string): FullVersion | undefined;
export function normaliseGameVersion(input: string | GameVersion): FullVersion | undefined {
const match = gameVersionRegExp.exec(input);
if (match === null) {
return undefined;
@@ -71,19 +71,8 @@ export function normaliseGameVersion(input: string): FullVersion | undefined {
return `${Number(match[1])}.${Number(match[2])}.${Number(match[3] ?? "0")}` as FullVersion;
}
/**
* Integer representation of a raw {@link GameVersion}, normalised the same
* lenient way the game reads it. Suitable for comparing versions that may carry
* leading zeros, whitespace or trailing content.
*
* @throws if the input can not be read as a version.
*/
export function integerGameVersion(version: GameVersion) {
const normalised = normaliseGameVersion(version);
if (normalised === undefined) {
throw new Error(`Invalid version "${version}"`);
}
return integerFullVersion(normalised);
return integerFullVersion(normaliseGameVersion(version));
}
/**
@@ -287,7 +276,10 @@ export class ModVersionRange {
return this.minVersion.testIntegerVersion(other) && this.maxVersion.testIntegerVersion(other);
}
testVersion(version: GameVersion) {
testVersion(version: PartialVersion | GameVersion) {
if (isPartialVersion(version)) {
return this.testIntegerVersion(integerPartialVersion(version));
}
return this.testIntegerVersion(integerGameVersion(version));
}
@@ -405,7 +405,7 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) {
missing.map(mod => ({
name: mod.name,
// mod.version is the raw version; the equality bound must be canonical.
version: new lib.ModVersionEquality("=", lib.normaliseGameVersion(mod.version)!),
version: new lib.ModVersionEquality("=", mod.normalisedVersion),
})),
factorioVersion,
)
+1 -1
View File
@@ -170,7 +170,7 @@ function SearchModsButton() {
if (modVersion) {
control.send(
new lib.ModPortalDownloadRequest(
[{ name: modName, version: new lib.ModVersionEquality("=", lib.normaliseGameVersion(modVersion)!)}],
[{ name: modName, version: new lib.ModVersionEquality("=", lib.normaliseGameVersion(modVersion))}],
portalFactorioVersion
)
).then(() => {