Port remaining lib/data modules to TypeScript

This commit is contained in:
Hornwitser committed 2023-08-11 01:13:38 +02:00
1 parent 2a52a82fb7
commit 679c00527f
6 files changed
+156 -227

No files matched your search

-46
View File
@@ -1,46 +0,0 @@
"use strict";
/**
* Export manifest for a mod pack
*
* Tracks files stored on disk for data that extracted from Factorio using a
* given mod pack.
* @alias module:lib.ExportManifest
*/
class ExportManifest {
/**
* Mapping between known assets and their file names.
*
* Currently exported assets are
* - settings: JSON of mod settings prototypes.
* - prototypes: JSON of all game prototypes except settings.
* - item-spritesheet: PNG containing icons from the game.
* - item-metadata: JSON of Map entries describing icons in item-spritesheet
* - locale: JSON of flattened Map entries of en locale strings from the game.
* @type {Object<string, string>}
*/
assets = {};
constructor(assets) {
if (assets) { this.assets = assets; }
}
static jsonSchema = {
type: "object",
additionalProperties: false,
required: ["assets"],
properties: {
"assets": {
type: "object",
additionalProperties: { type: "string" },
},
},
};
static fromJSON(json) {
return new this(json.assets);
}
}
module.exports = ExportManifest;
+33
View File
@@ -0,0 +1,33 @@
import { Type, Static } from "@sinclair/typebox";
/**
* Export manifest for a mod pack
*
* Tracks files stored on disk for data that extracted from Factorio using a
* given mod pack.
*/
export default class ExportManifest {
constructor(
/**
* Mapping between known assets and their file names.
*
* Currently exported assets are
* - settings: JSON of mod settings prototypes.
* - prototypes: JSON of all game prototypes except settings.
* - item-spritesheet: PNG containing icons from the game.
* - item-metadata: JSON of Map entries describing icons in item-spritesheet
* - locale: JSON of flattened Map entries of en locale strings from the game.
*/
public assets: Record<string, string>
) {
if (assets) { this.assets = assets; }
}
static jsonSchema = Type.Object({
"assets": Type.Record(Type.String(), Type.String()),
});
static fromJSON(json: Static<typeof ExportManifest.jsonSchema>) {
return new this(json.assets);
}
}
@@ -1,13 +1,12 @@
"use strict";
const fs = require("fs-extra");
const JSZip = require("jszip");
const path = require("path");
import fs from "fs-extra";
import JSZip from "jszip";
import { Type, Static } from "@sinclair/typebox";
const libHash = require("../hash");
const libSchema = require("../schema");
const { findRoot } = require("../zip_ops");
import * as libHash from "../hash";
import * as libSchema from "../schema";
import { findRoot } from "../zip_ops";
const { integerModVersion, integerFactorioVersion, modVersionRegExp } = require("./version");
import { integerModVersion, integerFactorioVersion, modVersionRegExp } from "./version";
/**
@@ -15,27 +14,23 @@ const { integerModVersion, integerFactorioVersion, modVersionRegExp } = require(
*
* See https://wiki.factorio.com/Tutorial:Mod_structure#info.json for
* details of the invidiual fields sourced from info.json.
* @alias module:lib.ModInfo
*/
class ModInfo {
export default class ModInfo {
/**
* Internal name of this mod.
* This is the name of the zip file as well as the name that appears in
* info.json.
* @type {string}
*/
name = "";
/**
* Version of the mod.
* Sourced from info.json.
* @type {string}
*/
version = "";
/**
* Integer representation of the version
* @type {number}
*/
get integerVersion() {
return integerModVersion(this.version);
@@ -44,42 +39,36 @@ class ModInfo {
/**
* Display name of the mod.
* Sourced from info.json.
* @type {string}
*/
title = "";
/**
* Author of the mod.
* Sourced from info.json.
* @type {string}
*/
author = "";
/**
* Contact field for mod.
* Sourced from info.json.
* @type {string}
*/
contact = "";
/**
* Homepage of the mod.
* Sourced from info.json.
* @type {string}
*/
homepage = "";
/**
* Description of the mod.
* Sourced from info.json.
* @type {string}
*/
description = "";
/**
* Major version of Factorio this mod supports.
* Sourced from info.json.
* @type {string}
*/
factorioVersion = "0.12";
@@ -94,13 +83,11 @@ class ModInfo {
/**
* Dependiences for this mod.
* Sourced from info.json.
* @type {Array<string>}
*/
dependencies = ["base"];
/**
* Expected name of zip file containing this mod.
* @type {number}
*/
get filename() {
return `${this.name}_${this.version}.zip`;
@@ -108,54 +95,44 @@ class ModInfo {
/**
* Size of the mod in bytes
* @type {number}
*/
size = 0;
/**
* SHA1 hash of this mod
* @type {string=}
*/
sha1;
sha1: string;
/**
* True if this mod has been deleted
* @type {boolean}
*/
isDeleted = false;
// Content of info.json found in mod files
static infoJsonSchema = {
type: "object",
required: ["name", "version", "title", "author"],
properties: {
"name": { type: "string" },
"version": { type: "string" },
"title": { type: "string" },
"author": { type: "string" },
"contact": { type: "string" },
"homepage": { type: "string" },
"description": { type: "string" },
"factorio_version": { type: "string" },
"dependencies": { type: "array", items: { type: "string" } },
},
};
static infoJsonSchema = Type.Object({
"name": Type.String(),
"version": Type.String(),
"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(Type.String()),
"dependencies": Type.Optional(Type.Array(Type.String())),
});
static validateInfo = libSchema.compile(this.infoJsonSchema);
static validateInfo = libSchema.compile<Static<typeof this.infoJsonSchema>>(this.infoJsonSchema as any);
static jsonSchema = {
type: "object",
properties: {
...this.infoJsonSchema.properties,
"size": { type: "integer" },
"sha1": { type: "string" },
"is_deleted": { type: "boolean" },
},
};
static jsonSchema = Type.Object({
...this.infoJsonSchema.properties,
"size": Type.Optional(Type.Integer()),
"sha1": Type.Optional(Type.String()),
"is_deleted": Type.Optional(Type.Boolean()),
});
static validate = libSchema.compile(this.jsonSchema);
static validate = libSchema.compile(this.jsonSchema as any);
static fromJSON(json) {
static fromJSON(json: Static<typeof ModInfo.jsonSchema>) {
const modInfo = new this();
// info.json fields
@@ -178,7 +155,7 @@ class ModInfo {
}
toJSON() {
let json = {
let json: Static<typeof ModInfo.jsonSchema> = {
name: this.name,
version: this.version,
title: this.title,
@@ -187,7 +164,7 @@ 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.factorio_version !== "0.12") { json.factorio_version = this.factorioVersion; }
if (this.factorioVersion !== "0.12") { json.factorio_version = this.factorioVersion; }
if (this.dependencies.length !== 1 || this.dependencies[0] !== "base") {
json.dependencies = this.dependencies;
}
@@ -198,7 +175,7 @@ class ModInfo {
}
static async fromModFile(modPath) {
let modInfo;
let modInfo: Static<typeof ModInfo.jsonSchema>;
{
// XXX: JSZip needs the whole archive loaded in memory to work.
// This is clearly untenable and will be replaced later.
@@ -231,5 +208,3 @@ class ModInfo {
});
}
}
module.exports = ModInfo;
@@ -1,84 +1,81 @@
"use strict";
const zlib = require("zlib");
import zlib from "zlib";
import { Type, Static } from "@sinclair/typebox";
const libSchema = require("../schema");
import * as libSchema from "../schema";
const ExportManifest = require("./ExportManifest");
const { integerFactorioVersion } = require("./version");
import ExportManifest from "./ExportManifest";
import { integerFactorioVersion } from "./version";
import type { Logger } from "../logging";
/**
* A setting for a mod.
* @typedef {object} module:lib.ModPack~ModSetting
* @property {boolean|number|string} value - Value of the given mod setting.
*/
const ModSettingJsonSchema = {
type: "object",
additionalProperties: false,
required: ["value"],
properties: {
"value": { type: ["boolean", "number", "string"] },
},
};
interface ModSetting {
/** Value of the given mod setting. */
value: boolean | number | string;
}
const ModSettingsJsonSchema = {
type: "object",
additionalProperties: ModSettingJsonSchema,
};
const ModSettingJsonSchema = Type.Object({
"value": Type.Union([Type.Boolean(), Type.Number(), Type.String()]),
});
const ModSettingsJsonSchema = Type.Record(Type.String(), ModSettingJsonSchema);
/**
* A mod in a mod pack
* @typedef {object} module:lib.ModPack~ModRecord
* @property {string} name - name of the mod.
* @property {boolean} enabled - if mod is to be loaded.
* @property {string} version - version of the mod.
* @property {string=} sha1 - SHA1 hash of the zip file.
*/
const ModRecordJsonSchema = {
type: "object",
required: ["name", "enabled", "version"],
properties: {
"name": { type: "string" },
"enabled": { type: "boolean" },
"version": { type: "string" },
"sha1": { type: "string" },
},
};
interface ModRecord {
/** name of the mod. */
name: string,
/** if mod is to be loaded. */
enabled: boolean,
/** version of the mod. */
version: string,
/** SHA1 hash of the zip file. */
sha1?: string,
}
const ModRecordJsonSchema = Type.Object({
"name": Type.String(),
"enabled": Type.Boolean(),
"version": Type.String(),
"sha1": Type.Optional(Type.String()),
});
type PropertyType = null | boolean | number | string | PropertyType[] | { [key: string]: PropertyType };
/**
* Factorio Mod Pack
*
* Tracks mods and settings for a collection of Factorio mods.
* @alias module:lib.ModPack
*/
class ModPack {
export default class ModPack {
declare ["constructor"]: typeof ModPack;
/**
* Id of this mod pack
* @type {number}
*/
id;
id: number;
/**
* Name of this mod pack.
* @type {string}
*/
name = "";
/**
* Description for this modpcak.
* @type {string}
*/
description = "";
/**
* Version of Factorio this mod pack is for
* @type {string}
*/
factorioVersion = "1.1.0";
/**
* Integer representation of the factorioVersion
* @type {number}
*/
get integerFactorioVersion() {
return integerFactorioVersion(this.factorioVersion);
@@ -86,29 +83,25 @@ class ModPack {
/**
* Mods included in this mod pack
* @type {Map<string, module:lib.ModPack~ModRecord>}
*/
mods = new Map();
mods = new Map<string, ModRecord>();
/**
* Mod settings for this mod pack
* @type {Object<string, Map<string, module:lib.ModPack~ModSetting>>=}
*/
settings = {
"startup": new Map(),
"runtime-global": new Map(),
"runtime-per-user": new Map(),
"startup": new Map<string, ModSetting>(),
"runtime-global": new Map<string, ModSetting>(),
"runtime-per-user": new Map<string, ModSetting>(),
};
/**
* Mapping to files containing exported data for this modpack
* @type {module:lib.ExportManifest|undefined}
*/
exportManifest;
exportManifest?: ExportManifest;
/**
* True if this mod pack has been deleted from the list of mod packs.
* @type {boolean}
*/
isDeleted = false;
@@ -125,37 +118,24 @@ class ModPack {
return clone;
}
static jsonSchema = {
type: "object",
additionalProperties: false,
required: ["name", "description", "factorio_version", "mods", "settings"],
properties: {
"id": { type: "integer" },
"name": { type: "string" },
"description": { type: "string" },
"factorio_version": { type: "string" },
"mods": {
type: "array",
items: ModRecordJsonSchema,
},
"settings": {
type: "object",
additionalProperties: false,
required: ["startup", "runtime-global", "runtime-per-user"],
properties: {
"startup": ModSettingsJsonSchema,
"runtime-global": ModSettingsJsonSchema,
"runtime-per-user": ModSettingsJsonSchema,
},
},
"export_manifest": ExportManifest.jsonSchema,
"is_deleted": { type: "boolean" },
},
};
static jsonSchema = Type.Object({
"id": Type.Optional(Type.Integer()),
"name": Type.String(),
"description": Type.String(),
"factorio_version": Type.String(),
"mods": Type.Array(ModRecordJsonSchema),
"settings": Type.Object({
"startup": ModSettingsJsonSchema,
"runtime-global": ModSettingsJsonSchema,
"runtime-per-user": ModSettingsJsonSchema,
}),
"export_manifest": Type.Optional(ExportManifest.jsonSchema),
"is_deleted": Type.Optional(Type.Boolean()),
});
static validate = libSchema.compile(this.jsonSchema);
static validate = libSchema.compile<Static<typeof ModPack.jsonSchema>>(this.jsonSchema as any);
static fromJSON(json) {
static fromJSON(json: Static<typeof ModPack.jsonSchema>) {
const modPack = new this();
if (json.id) {
@@ -185,7 +165,7 @@ class ModPack {
}
toJSON() {
let json = {
let json: Static<typeof ModPack.jsonSchema> = {
id: this.id,
name: this.name,
description: this.description,
@@ -212,7 +192,7 @@ class ModPack {
return buf.toString("base64");
}
static fromModPackString(modPackString) {
static fromModPackString(modPackString: string) {
let buf = Buffer.from(modPackString, "base64");
try {
// eslint-disable-next-line node/no-sync
@@ -223,9 +203,9 @@ class ModPack {
}
}
let json;
let json: unknown;
try {
json = JSON.parse(buf);
json = JSON.parse(buf.toString());
} catch (err) {
throw new Error(`Malformed mod pack string: ${err.message}`);
}
@@ -238,16 +218,16 @@ class ModPack {
}
toModSettingsDat() {
function uint8Byte(int) {
function uint8Byte(int: number) {
return Buffer.from(Uint8Array.from([int]).buffer);
}
function int16Bytes(int) {
function int16Bytes(int: number) {
return Buffer.from(Int16Array.from([int]).buffer);
}
function uint32Bytes(int) {
function uint32Bytes(int: number) {
return Buffer.from(Uint32Array.from([int]).buffer);
}
function uint32SpaceOptimizedBytes(int) {
function uint32SpaceOptimizedBytes(int: number) {
if (0 <= int && int < 0xff) {
return uint8Byte(int);
}
@@ -256,17 +236,17 @@ class ModPack {
uint32Bytes(int),
]);
}
function doubleBytes(double) {
function doubleBytes(double: number) {
return Buffer.from(Float64Array.from([double]).buffer);
}
function stringBytes(str) {
function stringBytes(str: string) {
const buf = Buffer.from(str, "utf8");
return Buffer.concat([
uint32SpaceOptimizedBytes(buf.length),
buf,
]);
}
function immutableStringBytes(str) {
function immutableStringBytes(str: string) {
if (str === undefined) {
return uint8Byte(1); // empty
}
@@ -275,7 +255,7 @@ class ModPack {
stringBytes(str),
]);
}
function versionBytes(version) {
function versionBytes(version: string) {
const [main, major, minor] = version.split(".").map(n => Number.parseInt(n, 10));
return Buffer.concat([
int16Bytes(main),
@@ -285,7 +265,7 @@ class ModPack {
Buffer.alloc(1), // reserved byte
]);
}
function propertyTreeListBytes(entries) {
function propertyTreeListBytes(entries: [string, PropertyType][]) {
const sizeBytes = uint32Bytes(entries.length);
const itemBytes = entries.flatMap(([key, item]) => [
immutableStringBytes(key),
@@ -297,15 +277,15 @@ class ModPack {
...itemBytes,
]);
}
function propertyTreeBytes(element) {
let type;
let dataBytes;
function propertyTreeBytes(element: PropertyType) {
let type: number;
let dataBytes: Buffer;
if (typeof element === null) {
type = 1;
dataBytes = Buffer.alloc(0);
} else if (typeof element === "boolean") {
type = 1;
dataBytes = uint8Byte(element);
dataBytes = uint8Byte(Number(element));
} else if (typeof element === "number") {
type = 2;
dataBytes = doubleBytes(element);
@@ -340,11 +320,11 @@ class ModPack {
* Uses the provided setting prototypes to add any missing mod settings
* in the mod pack with the default value from the prototype.
*
* @param {Object<string, object>} settingPrototypes -
* @param settingPrototypes -
* Setting prototypes exported from the game.
* @param {Logger} logger - Logger used to report warnings on.
* @param logger - Logger used to report warnings on.
*/
fillDefaultSettings(settingPrototypes, logger) {
fillDefaultSettings(settingPrototypes: Record<string, object>, logger: Logger) {
const knownTypes = ["bool-setting", "int-setting", "double-setting", "string-setting"];
let prototypes = Object.entries(settingPrototypes)
.filter(([type, _]) => knownTypes.includes(type))
@@ -368,5 +348,3 @@ class ModPack {
}
}
}
module.exports = ModPack;
+5 -5
View File
@@ -56,7 +56,7 @@ export class ModPackCreateRequest {
) { }
static jsonSchema = Type.Object({
"modPack": Type.Unsafe<object>(ModPack.jsonSchema),
"modPack": ModPack.jsonSchema,
});
static fromJSON(json: Static<typeof this.jsonSchema>) {
@@ -76,7 +76,7 @@ export class ModPackUpdateRequest {
) { }
static jsonSchema = Type.Object({
"modPack": Type.Unsafe<object>(ModPack.jsonSchema),
"modPack": ModPack.jsonSchema,
});
static fromJSON(json: Static<typeof this.jsonSchema>) {
@@ -203,7 +203,7 @@ export class ModSearchRequest {
"results": Type.Array(
Type.Object({
"name": Type.String(),
"versions": Type.Array(Type.Unsafe<object>(ModInfo.jsonSchema)),
"versions": Type.Array(ModInfo.jsonSchema),
}),
),
});
@@ -296,7 +296,7 @@ export class ModPackUpdateEvent {
) { }
static jsonSchema = Type.Object({
"modPack": Type.Unsafe<object>(ModPack.jsonSchema),
"modPack": ModPack.jsonSchema,
});
static fromJSON(json: Static<typeof this.jsonSchema>) {
@@ -315,7 +315,7 @@ export class ModUpdateEvent {
) { }
static jsonSchema = Type.Object({
"mod": Type.Unsafe<object>(ModInfo.jsonSchema),
"mod": ModInfo.jsonSchema,
});
static fromJSON(json: Static<typeof this.jsonSchema>) {
@@ -1,5 +1,4 @@
// Helper functions for dealing with factorio version numbers
"use strict";
/**
@@ -7,11 +6,10 @@
*
* Suitable for sorting and comparing version numbers which may have different
* number of leading zeros in them.
* @param {string} version - string representation of version.
* @returns {number} numeric representation.
* @alias module:lib.integerModVersion
* @param version - string representation of version.
* @returns numeric representation.
*/
function integerModVersion(version) {
export function integerModVersion(version: string) {
const [major, minor, sub] = version.split(".").map(n => Number.parseInt(n, 10));
return major * 0x100000000 + minor * 0x10000 + sub; // Can't use bitwise here because this is 48-bits.
}
@@ -21,24 +19,15 @@ function integerModVersion(version) {
*
* Suitable for sorting and comparing version numbers which may have different
* number of leading zeros in them.
* @param {string} version - string representation of version.
* @returns {number} numeric representation.
* @alias module:lib.integerFactorioVersion
* @param version - string representation of version.
* @returns numeric representation.
*/
function integerFactorioVersion(version) {
export function integerFactorioVersion(version: string) {
const [main, major, minor] = version.split(".").map(n => Number.parseInt(n, 10));
return main * 0x100000000 + major * 0x10000 + (minor || 0); // Can't use bitwise here because this is 48-bits.
}
/**
* Matches valid mod versions.
* @type {RegExp}
* @alias module:lib.modVersionRegExp
*/
const modVersionRegExp = /^\d+\.\d+\.\d+$/;
module.exports = {
integerModVersion,
integerFactorioVersion,
modVersionRegExp,
};
export const modVersionRegExp = /^\d+\.\d+\.\d+$/;