From b7fa4fab6766fe76aa2e1c28c3baa595b7476a3d Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Fri, 25 Nov 2022 01:37:53 +0300 Subject: [PATCH] `QuiltModMetadata` refactoring --- README.md | 30 +-- src/metadata/quilt/quilt-mod-config.ts | 66 +++++++ .../quilt/quilt-mod-metadata-reader.ts | 15 +- src/metadata/quilt/quilt-mod-metadata.ts | 176 +++++++++++------- test/content/quilt/quilt.mod.json | 31 ++- .../metadata/mod-metadata-reader.test.ts | 6 + 6 files changed, 211 insertions(+), 113 deletions(-) create mode 100644 src/metadata/quilt/quilt-mod-config.ts diff --git a/README.md b/README.md index ec3061b..2c8e0e7 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ Can be automatically retrieved from the config file of your mod: - `quilt.mod.json` (Quilt) - - `mc-publish` field *(recommended)*: + - Custom `mc-publish` field: ```json { // ... @@ -211,16 +211,6 @@ Can be automatically retrieved from the config file of your mod: } ``` - - `projects` field: - ```json - { - // ... - "projects": { - "modrinth": "AANobbMI" - }, - } - ``` - #### modrinth-token A valid token for the Modrinth API. It's required if you want to publish your assets to Modrinth. @@ -307,7 +297,7 @@ Can be automatically retrieved from the config file of your mod: - `quilt.mod.json` (Quilt) - - `mc-publish` field *(recommended)*: + - Custom `mc-publish` field: ```json { // ... @@ -317,16 +307,6 @@ Can be automatically retrieved from the config file of your mod: } ``` - - `projects` field: - ```json - { - // ... - "projects": { - "curseforge": 394468 - }, - } - ``` - #### curseforge-token A valid token for the CurseForge API. It's required if you want to publish your assets to CurseForge. @@ -613,7 +593,7 @@ Can be automatically retrieved from the config file of your mod: "required-dependency", { "id": "optional-dependency", - "version": "0.1.0", + "versions": "0.1.0", "optional": true } ], @@ -631,11 +611,11 @@ Can be automatically retrieved from the config file of your mod: "breaks": [ { "id": "incompatible-dependency", - "version": "*" + "versions": "*" }, { "id": "conflicting-dependency", - "version": "*", + "versions": "*", "unless": "some-mod-that-fixes-conflict" } ], diff --git a/src/metadata/quilt/quilt-mod-config.ts b/src/metadata/quilt/quilt-mod-config.ts new file mode 100644 index 0000000..f0d3692 --- /dev/null +++ b/src/metadata/quilt/quilt-mod-config.ts @@ -0,0 +1,66 @@ +type Plugin = string | { adapter?: string, value: string }; + +type Entrypoint = Plugin; + +type License = string | { + name: string; + id: string; + url: string; + description?: string; +}; + +type Dependency = string | { + id: string; + version?: string; + versions?: string | string[]; + reason?: string; + optional?: boolean; + unless?: Dependency | Dependency[]; +}; + +// https://github.com/QuiltMC/rfcs/blob/main/specification/0002-quilt.mod.json.md +type QuiltModConfig = { + schema_version: 1; + + quilt_loader: { + group: string; + id: string; + provides?: Dependency[]; + version: string; + entrypoints?: Record; + plugins?: Plugin[]; + jars?: string[]; + language_adapters?: Record; + depends?: Dependency[]; + breaks?: Dependency[]; + load_type?: "always" | "if_possible" | "if_required"; + repositories?: string[]; + intermediate_mappings?: string; + metadata?: Record; + name?: string; + description?: string; + contributors?: Record; + contact?: { + email?: string; + homepage?: string; + issues?: string; + sources?: string; + [key: string]: string; + }; + license?: License | License[]; + icon?: string | Record; + + }; + + mixin?: string | string[]; + access_widener?: string | string[]; + minecraft?: { + environment?: "client" | "dedicated_server" | "*"; + }; +} & Record; + +namespace QuiltModConfig { + export const FILENAME = "quilt.mod.json"; +} + +export default QuiltModConfig; diff --git a/src/metadata/quilt/quilt-mod-metadata-reader.ts b/src/metadata/quilt/quilt-mod-metadata-reader.ts index caac99a..b934a56 100644 --- a/src/metadata/quilt/quilt-mod-metadata-reader.ts +++ b/src/metadata/quilt/quilt-mod-metadata-reader.ts @@ -1,17 +1,20 @@ -import ModMetadata from "../../metadata/mod-metadata"; -import ZippedModMetadataReader from "../../metadata/zipped-mod-metadata-reader"; +import ModMetadata from "../mod-metadata"; +import ZippedModMetadataReader from "../zipped-mod-metadata-reader"; +import QuiltModConfig from "./quilt-mod-config"; import QuiltModMetadata from "./quilt-mod-metadata"; -export default class QuiltModMetadataReader extends ZippedModMetadataReader { +class QuiltModMetadataReader extends ZippedModMetadataReader { constructor() { - super("quilt.mod.json"); + super(QuiltModConfig.FILENAME); } - protected loadConfig(buffer: Buffer): Record { + protected loadConfig(buffer: Buffer): QuiltModConfig { return JSON.parse(buffer.toString("utf8")); } - protected createMetadataFromConfig(config: Record): ModMetadata { + protected createMetadataFromConfig(config: QuiltModConfig): ModMetadata { return new QuiltModMetadata(config); } } + +export default QuiltModMetadataReader; diff --git a/src/metadata/quilt/quilt-mod-metadata.ts b/src/metadata/quilt/quilt-mod-metadata.ts index 92dd098..a806756 100644 --- a/src/metadata/quilt/quilt-mod-metadata.ts +++ b/src/metadata/quilt/quilt-mod-metadata.ts @@ -1,24 +1,41 @@ import action from "../../../package.json"; -import Dependency from "../../metadata/dependency"; -import DependencyKind from "../../metadata/dependency-kind"; -import ModConfig from "../../metadata/mod-config"; -import ModConfigDependency from "../../metadata/mod-config-dependency"; +import Dependency from "../dependency"; +import DependencyKind from "../dependency-kind"; import PublisherTarget from "../../publishing/publisher-target"; +import ModMetadata from "../mod-metadata"; +import QuiltModConfig from "./quilt-mod-config"; -function extractId(id?: string): string | null { +type Aliases = Map; + +type QuiltDependency = QuiltModConfig["quilt_loader"]["breaks"][number]; + +type ExtendedQuiltDependency = QuiltDependency & { + embedded?: boolean; + incompatible?: boolean; +}; + +function getDependencies(config: QuiltModConfig): Dependency[] { + const root = config.quilt_loader; + return getExtendedDependencyEntries(root.depends) + .concat(getExtendedDependencyEntries(root.provides, x => x.embedded = true)) + .concat(getExtendedDependencyEntries(root.breaks, x => x.incompatible = true)) + .map(parseDependency) + .filter((x, i, self) => self.findIndex(y => x.id === y.id && x.kind === y.kind) === i); +} + +function parseId(id?: string): string | null { if (!id) { return id ?? null; } const separatorIndex = id.indexOf(":"); - if (separatorIndex !== -1) { - id = id.substring(separatorIndex + 1); + if (separatorIndex === -1) { + return id; } - - return id; + return id.substring(separatorIndex + 1); } -function getDependencyEntries(container: any, transformer?: (x: any) => void): any[] { +function getExtendedDependencyEntries(container: QuiltDependency[], transformer?: (x: ExtendedQuiltDependency) => void): ExtendedQuiltDependency[] { if (!Array.isArray(container)) { return []; } @@ -30,68 +47,101 @@ function getDependencyEntries(container: any, transformer?: (x: any) => void): a return container; } -const ignoredByDefault = ["minecraft", "java", "quilt_loader"]; -const aliases = new Map([ +function parseDependency(body: ExtendedQuiltDependency): Dependency { + const id = parseId(typeof body === "string" ? body : String(body.id ?? "")); + const ignoredByDefault = isDependencyIgnoredByDefault(id); + const defaultAliases = getDefaultDependencyAliases(id); + + if (typeof body === "string") { + return Dependency.create({ id, ignore: ignoredByDefault, aliases: defaultAliases }); + } + + const version = body.version ?? (Array.isArray(body.versions) ? body.versions.join(" || ") : body.versions || "*"); + const kind = ( + body.incompatible && body.unless && DependencyKind.Conflicts || + body.incompatible && DependencyKind.Breaks || + body.embedded && DependencyKind.Includes || + body.optional && DependencyKind.Recommends || + DependencyKind.Depends + ); + const ignore = body[action.name]?.ignore ?? ignoredByDefault; + + const aliases = new Map([...(defaultAliases || [])]); + for (const target of PublisherTarget.getValues()) { + const targetName = PublisherTarget.toString(target).toLowerCase(); + const alias = body[action.name]?.[targetName]; + if (alias) { + aliases.set(target, String(alias)); + } + } + + return Dependency.create({ id, version, kind, ignore, aliases }); +} + +const ignoredByDefault = [ + "minecraft", + "java", + "quilt_loader", +]; +function isDependencyIgnoredByDefault(id: string): boolean { + return ignoredByDefault.includes(id); +} + +const defaultAliases = new Map([ ["fabric", "fabric-api"], ["quilted_fabric_api", "qsl"], ]); -function createDependency(body: any): Dependency { - const id = extractId(typeof body === "string" ? body : String(body.id ?? "")); - const ignore = ignoredByDefault.includes(id); - if (id.startsWith("quilted_") || id.startsWith("quilt_")) { - aliases.set(id, "qsl"); +function getDefaultDependencyAliases(id: string): Aliases | null { + if (id.startsWith("quilted_")) { + id = "quilted_fabric_api"; } - if (typeof body === "string") { - const dependencyAliases = aliases.has(id) ? new Map(PublisherTarget.getValues().map(x => [x, aliases.get(id)])) : null; - return Dependency.create({ id, ignore, aliases: dependencyAliases }); + if (!defaultAliases.has(id)) { + return null; } - const dependencyMetadata = { - ignore, - ...body, - id, - version: body.version ?? String(Array.isArray(body.versions) ? body.versions[0] : body.versions || "*"), - kind: ( - body.incompatible && body.unless && DependencyKind.Conflicts || - body.incompatible && DependencyKind.Breaks || - body.embedded && DependencyKind.Includes || - body.optional && DependencyKind.Recommends || - DependencyKind.Depends - ) - }; - if (aliases.has(id)) { - if (!dependencyMetadata[action.name]) { - dependencyMetadata[action.name] = {}; - } - for (const target of PublisherTarget.getValues()) { - const targetName = PublisherTarget.toString(target).toLowerCase(); - if (typeof dependencyMetadata[action.name][targetName] !== "string") { - dependencyMetadata[action.name][targetName] = aliases.get(id); - } - } + const aliases = defaultAliases.get(id); + if (typeof aliases !== "string") { + return new Map([...aliases]); } - return new ModConfigDependency(dependencyMetadata); + + return new Map(PublisherTarget.getValues().map(x => [x, aliases])); } -export default class QuiltModMetadata extends ModConfig { - public readonly id: string; - public readonly name: string; - public readonly version: string; - public readonly loaders: string[]; - public readonly dependencies: Dependency[]; +function getProjects(config: QuiltModConfig): Map { + const projects = new Map(); + for (const target of PublisherTarget.getValues()) { + const targetName = PublisherTarget.toString(target).toLowerCase(); + const projectId = config[action.name]?.[targetName] ?? config.projects?.[targetName]; - constructor(config: Record) { - super(config); - const root = >this.config.quilt_loader ?? {}; - this.id = String(root.id ?? ""); - this.name = String(root.name ?? this.id); - this.version = String(root.version ?? "*"); - this.loaders = ["quilt"]; - this.dependencies = getDependencyEntries(root.depends) - .concat(getDependencyEntries(root.provides, x => x.embedded = true)) - .concat(getDependencyEntries(root.breaks, x => x.incompatible = true)) - .map(createDependency) - .filter((x, i, self) => self.findIndex(y => x.id === y.id && x.kind === y.kind) === i); + if (projectId) { + projects.set(target, String(projectId)); + } } -} \ No newline at end of file + return projects; +} + +class QuiltModMetadata implements ModMetadata { + readonly id: string; + readonly name: string; + readonly version: string; + readonly loaders: string[]; + readonly dependencies: Dependency[]; + + private readonly _projects: Map; + + constructor(config: QuiltModConfig) { + this.id = String(config.quilt_loader.id ?? ""); + this.name = String(config.quilt_loader.name ?? this.id); + this.version = String(config.quilt_loader.version ?? "*"); + this.loaders = ["quilt"]; + this.dependencies = getDependencies(config); + this._projects = getProjects(config); + } + + getProjectId(project: PublisherTarget): string | undefined { + return this._projects.get(project); + } +} + +export default QuiltModMetadata; diff --git a/test/content/quilt/quilt.mod.json b/test/content/quilt/quilt.mod.json index 1b3f1e1..0d930ff 100644 --- a/test/content/quilt/quilt.mod.json +++ b/test/content/quilt/quilt.mod.json @@ -27,35 +27,32 @@ "depends": [ { "id": "quilt_loader", - "version": ">=0.11.3" + "versions": ">=0.11.3" }, { "id": "quilt_base", - "version": ">=0.40.0" + "versions": ">=0.40.0" }, { "id": "minecraft", - "version": "1.17.x" + "versions": [ + "1.17", + "1.17.1" + ] }, { "id": "java", - "version": ">=16" + "versions": ">=16" }, { "id": "recommended-mod", - "version": "0.2.0", + "versions": "0.2.0", "optional": true, "mc-publish": { + "curseforge": 42, + "github": "v0.2.0", "modrinth": "AAAA", "ignore": true - }, - "projects": { - "curseforge": 42 - }, - "custom": { - "projects": { - "github": "v0.2.0" - } } } ], @@ -66,22 +63,18 @@ "breaking-mod", { "id": "conflicting:conflicting-mod", - "version": "<0.40.0", + "versions": "<0.40.0", "unless": "fix-conflicting-mod" } ] }, "mc-publish": { + "github": "mc1.18-0.4.0-alpha5", "modrinth": "AANobbMI" }, "projects": { "curseforge": 394468 }, - "custom": { - "projects": { - "github": "mc1.18-0.4.0-alpha5" - } - }, "mixins": [ "example-mod.mixins.json" ], diff --git a/test/unit-tests/metadata/mod-metadata-reader.test.ts b/test/unit-tests/metadata/mod-metadata-reader.test.ts index 56add9d..060baac 100644 --- a/test/unit-tests/metadata/mod-metadata-reader.test.ts +++ b/test/unit-tests/metadata/mod-metadata-reader.test.ts @@ -233,6 +233,12 @@ describe("ModMetadataReader.readMetadata", () => { } }); + test("version array is supported", async () => { + const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!; + const minecraft = metadata.dependencies.find(x => x.id === "minecraft"); + expect(minecraft.version).toStrictEqual("1.17 || 1.17.1"); + }); + test("custom metadata can be attached to dependency entry", async () => { const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!; const recommended = metadata.dependencies.find(x => x.id === "recommended-mod")!;