From 679c00527f1655dabd94113c28602ac81c7393bd Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Fri, 11 Aug 2023 01:13:38 +0200 Subject: [PATCH] Port remaining lib/data modules to TypeScript --- packages/lib/src/data/ExportManifest.js | 46 ----- packages/lib/src/data/ExportManifest.ts | 33 ++++ .../lib/src/data/{ModInfo.js => ModInfo.ts} | 89 ++++----- .../lib/src/data/{ModPack.js => ModPack.ts} | 180 ++++++++---------- packages/lib/src/data/messages_mod.ts | 10 +- .../lib/src/data/{version.js => version.ts} | 25 +-- 6 files changed, 156 insertions(+), 227 deletions(-) delete mode 100644 packages/lib/src/data/ExportManifest.js create mode 100644 packages/lib/src/data/ExportManifest.ts rename packages/lib/src/data/{ModInfo.js => ModInfo.ts} (70%) rename packages/lib/src/data/{ModPack.js => ModPack.ts} (68%) rename packages/lib/src/data/{version.js => version.ts} (58%) diff --git a/packages/lib/src/data/ExportManifest.js b/packages/lib/src/data/ExportManifest.js deleted file mode 100644 index 9e52a725..00000000 --- a/packages/lib/src/data/ExportManifest.js +++ /dev/null @@ -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} - */ - 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; diff --git a/packages/lib/src/data/ExportManifest.ts b/packages/lib/src/data/ExportManifest.ts new file mode 100644 index 00000000..f1abc319 --- /dev/null +++ b/packages/lib/src/data/ExportManifest.ts @@ -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 + ) { + if (assets) { this.assets = assets; } + } + + static jsonSchema = Type.Object({ + "assets": Type.Record(Type.String(), Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.assets); + } +} diff --git a/packages/lib/src/data/ModInfo.js b/packages/lib/src/data/ModInfo.ts similarity index 70% rename from packages/lib/src/data/ModInfo.js rename to packages/lib/src/data/ModInfo.ts index 8ad802d1..ce9e46b4 100644 --- a/packages/lib/src/data/ModInfo.js +++ b/packages/lib/src/data/ModInfo.ts @@ -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} */ 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>(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) { const modInfo = new this(); // info.json fields @@ -178,7 +155,7 @@ class ModInfo { } toJSON() { - let json = { + let json: Static = { 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; { // 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; diff --git a/packages/lib/src/data/ModPack.js b/packages/lib/src/data/ModPack.ts similarity index 68% rename from packages/lib/src/data/ModPack.js rename to packages/lib/src/data/ModPack.ts index de4335f8..47dfce83 100644 --- a/packages/lib/src/data/ModPack.js +++ b/packages/lib/src/data/ModPack.ts @@ -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} */ - mods = new Map(); + mods = new Map(); /** * Mod settings for this mod pack - * @type {Object>=} */ settings = { - "startup": new Map(), - "runtime-global": new Map(), - "runtime-per-user": new Map(), + "startup": new Map(), + "runtime-global": new Map(), + "runtime-per-user": new Map(), }; /** * 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>(this.jsonSchema as any); - static fromJSON(json) { + static fromJSON(json: Static) { const modPack = new this(); if (json.id) { @@ -185,7 +165,7 @@ class ModPack { } toJSON() { - let json = { + let json: Static = { 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} 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, 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; diff --git a/packages/lib/src/data/messages_mod.ts b/packages/lib/src/data/messages_mod.ts index a32aac3f..a7c8c72c 100644 --- a/packages/lib/src/data/messages_mod.ts +++ b/packages/lib/src/data/messages_mod.ts @@ -56,7 +56,7 @@ export class ModPackCreateRequest { ) { } static jsonSchema = Type.Object({ - "modPack": Type.Unsafe(ModPack.jsonSchema), + "modPack": ModPack.jsonSchema, }); static fromJSON(json: Static) { @@ -76,7 +76,7 @@ export class ModPackUpdateRequest { ) { } static jsonSchema = Type.Object({ - "modPack": Type.Unsafe(ModPack.jsonSchema), + "modPack": ModPack.jsonSchema, }); static fromJSON(json: Static) { @@ -203,7 +203,7 @@ export class ModSearchRequest { "results": Type.Array( Type.Object({ "name": Type.String(), - "versions": Type.Array(Type.Unsafe(ModInfo.jsonSchema)), + "versions": Type.Array(ModInfo.jsonSchema), }), ), }); @@ -296,7 +296,7 @@ export class ModPackUpdateEvent { ) { } static jsonSchema = Type.Object({ - "modPack": Type.Unsafe(ModPack.jsonSchema), + "modPack": ModPack.jsonSchema, }); static fromJSON(json: Static) { @@ -315,7 +315,7 @@ export class ModUpdateEvent { ) { } static jsonSchema = Type.Object({ - "mod": Type.Unsafe(ModInfo.jsonSchema), + "mod": ModInfo.jsonSchema, }); static fromJSON(json: Static) { diff --git a/packages/lib/src/data/version.js b/packages/lib/src/data/version.ts similarity index 58% rename from packages/lib/src/data/version.js rename to packages/lib/src/data/version.ts index c20f44db..bd79bece 100644 --- a/packages/lib/src/data/version.js +++ b/packages/lib/src/data/version.ts @@ -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+$/;