mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-28 11:21:06 -05:00
QuiltModMetadata
refactoring
This commit is contained in:
parent
2fc40453de
commit
b7fa4fab67
6 changed files with 211 additions and 113 deletions
30
README.md
30
README.md
|
@ -201,7 +201,7 @@ Can be automatically retrieved from the config file of your mod:
|
||||||
|
|
||||||
- `quilt.mod.json` (Quilt)
|
- `quilt.mod.json` (Quilt)
|
||||||
|
|
||||||
- `mc-publish` field *(recommended)*:
|
- Custom `mc-publish` field:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
// ...
|
// ...
|
||||||
|
@ -211,16 +211,6 @@ Can be automatically retrieved from the config file of your mod:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `projects` field:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
"projects": {
|
|
||||||
"modrinth": "AANobbMI"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### modrinth-token
|
#### modrinth-token
|
||||||
|
|
||||||
A valid token for the Modrinth API. It's required if you want to publish your assets to Modrinth.
|
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)
|
- `quilt.mod.json` (Quilt)
|
||||||
|
|
||||||
- `mc-publish` field *(recommended)*:
|
- Custom `mc-publish` field:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
// ...
|
// ...
|
||||||
|
@ -317,16 +307,6 @@ Can be automatically retrieved from the config file of your mod:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `projects` field:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
"projects": {
|
|
||||||
"curseforge": 394468
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### curseforge-token
|
#### curseforge-token
|
||||||
|
|
||||||
A valid token for the CurseForge API. It's required if you want to publish your assets to CurseForge.
|
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",
|
"required-dependency",
|
||||||
{
|
{
|
||||||
"id": "optional-dependency",
|
"id": "optional-dependency",
|
||||||
"version": "0.1.0",
|
"versions": "0.1.0",
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -631,11 +611,11 @@ Can be automatically retrieved from the config file of your mod:
|
||||||
"breaks": [
|
"breaks": [
|
||||||
{
|
{
|
||||||
"id": "incompatible-dependency",
|
"id": "incompatible-dependency",
|
||||||
"version": "*"
|
"versions": "*"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "conflicting-dependency",
|
"id": "conflicting-dependency",
|
||||||
"version": "*",
|
"versions": "*",
|
||||||
"unless": "some-mod-that-fixes-conflict"
|
"unless": "some-mod-that-fixes-conflict"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
66
src/metadata/quilt/quilt-mod-config.ts
Normal file
66
src/metadata/quilt/quilt-mod-config.ts
Normal file
|
@ -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<string, Entrypoint | Entrypoint[]>;
|
||||||
|
plugins?: Plugin[];
|
||||||
|
jars?: string[];
|
||||||
|
language_adapters?: Record<string, string>;
|
||||||
|
depends?: Dependency[];
|
||||||
|
breaks?: Dependency[];
|
||||||
|
load_type?: "always" | "if_possible" | "if_required";
|
||||||
|
repositories?: string[];
|
||||||
|
intermediate_mappings?: string;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
contributors?: Record<string, string>;
|
||||||
|
contact?: {
|
||||||
|
email?: string;
|
||||||
|
homepage?: string;
|
||||||
|
issues?: string;
|
||||||
|
sources?: string;
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
license?: License | License[];
|
||||||
|
icon?: string | Record<string, string>;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
mixin?: string | string[];
|
||||||
|
access_widener?: string | string[];
|
||||||
|
minecraft?: {
|
||||||
|
environment?: "client" | "dedicated_server" | "*";
|
||||||
|
};
|
||||||
|
} & Record<string, any>;
|
||||||
|
|
||||||
|
namespace QuiltModConfig {
|
||||||
|
export const FILENAME = "quilt.mod.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QuiltModConfig;
|
|
@ -1,17 +1,20 @@
|
||||||
import ModMetadata from "../../metadata/mod-metadata";
|
import ModMetadata from "../mod-metadata";
|
||||||
import ZippedModMetadataReader from "../../metadata/zipped-mod-metadata-reader";
|
import ZippedModMetadataReader from "../zipped-mod-metadata-reader";
|
||||||
|
import QuiltModConfig from "./quilt-mod-config";
|
||||||
import QuiltModMetadata from "./quilt-mod-metadata";
|
import QuiltModMetadata from "./quilt-mod-metadata";
|
||||||
|
|
||||||
export default class QuiltModMetadataReader extends ZippedModMetadataReader {
|
class QuiltModMetadataReader extends ZippedModMetadataReader<QuiltModConfig> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("quilt.mod.json");
|
super(QuiltModConfig.FILENAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadConfig(buffer: Buffer): Record<string, unknown> {
|
protected loadConfig(buffer: Buffer): QuiltModConfig {
|
||||||
return JSON.parse(buffer.toString("utf8"));
|
return JSON.parse(buffer.toString("utf8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createMetadataFromConfig(config: Record<string, unknown>): ModMetadata {
|
protected createMetadataFromConfig(config: QuiltModConfig): ModMetadata {
|
||||||
return new QuiltModMetadata(config);
|
return new QuiltModMetadata(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default QuiltModMetadataReader;
|
||||||
|
|
|
@ -1,24 +1,41 @@
|
||||||
import action from "../../../package.json";
|
import action from "../../../package.json";
|
||||||
import Dependency from "../../metadata/dependency";
|
import Dependency from "../dependency";
|
||||||
import DependencyKind from "../../metadata/dependency-kind";
|
import DependencyKind from "../dependency-kind";
|
||||||
import ModConfig from "../../metadata/mod-config";
|
|
||||||
import ModConfigDependency from "../../metadata/mod-config-dependency";
|
|
||||||
import PublisherTarget from "../../publishing/publisher-target";
|
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<PublisherTarget, string>;
|
||||||
|
|
||||||
|
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) {
|
if (!id) {
|
||||||
return id ?? null;
|
return id ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const separatorIndex = id.indexOf(":");
|
const separatorIndex = id.indexOf(":");
|
||||||
if (separatorIndex !== -1) {
|
if (separatorIndex === -1) {
|
||||||
id = id.substring(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)) {
|
if (!Array.isArray(container)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -30,68 +47,101 @@ function getDependencyEntries(container: any, transformer?: (x: any) => void): a
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ignoredByDefault = ["minecraft", "java", "quilt_loader"];
|
function parseDependency(body: ExtendedQuiltDependency): Dependency {
|
||||||
const aliases = new Map([
|
const id = parseId(typeof body === "string" ? body : String(body.id ?? ""));
|
||||||
["fabric", "fabric-api"],
|
const ignoredByDefault = isDependencyIgnoredByDefault(id);
|
||||||
["quilted_fabric_api", "qsl"],
|
const defaultAliases = getDefaultDependencyAliases(id);
|
||||||
]);
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof body === "string") {
|
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: ignoredByDefault, aliases: defaultAliases });
|
||||||
return Dependency.create({ id, ignore, aliases: dependencyAliases });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dependencyMetadata = {
|
const version = body.version ?? (Array.isArray(body.versions) ? body.versions.join(" || ") : body.versions || "*");
|
||||||
ignore,
|
const kind = (
|
||||||
...body,
|
|
||||||
id,
|
|
||||||
version: body.version ?? String(Array.isArray(body.versions) ? body.versions[0] : body.versions || "*"),
|
|
||||||
kind: (
|
|
||||||
body.incompatible && body.unless && DependencyKind.Conflicts ||
|
body.incompatible && body.unless && DependencyKind.Conflicts ||
|
||||||
body.incompatible && DependencyKind.Breaks ||
|
body.incompatible && DependencyKind.Breaks ||
|
||||||
body.embedded && DependencyKind.Includes ||
|
body.embedded && DependencyKind.Includes ||
|
||||||
body.optional && DependencyKind.Recommends ||
|
body.optional && DependencyKind.Recommends ||
|
||||||
DependencyKind.Depends
|
DependencyKind.Depends
|
||||||
)
|
);
|
||||||
};
|
const ignore = body[action.name]?.ignore ?? ignoredByDefault;
|
||||||
if (aliases.has(id)) {
|
|
||||||
if (!dependencyMetadata[action.name]) {
|
const aliases = new Map([...(defaultAliases || [])]);
|
||||||
dependencyMetadata[action.name] = {};
|
|
||||||
}
|
|
||||||
for (const target of PublisherTarget.getValues()) {
|
for (const target of PublisherTarget.getValues()) {
|
||||||
const targetName = PublisherTarget.toString(target).toLowerCase();
|
const targetName = PublisherTarget.toString(target).toLowerCase();
|
||||||
if (typeof dependencyMetadata[action.name][targetName] !== "string") {
|
const alias = body[action.name]?.[targetName];
|
||||||
dependencyMetadata[action.name][targetName] = aliases.get(id);
|
if (alias) {
|
||||||
|
aliases.set(target, String(alias));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return new ModConfigDependency(dependencyMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class QuiltModMetadata extends ModConfig {
|
return Dependency.create({ id, version, kind, ignore, aliases });
|
||||||
public readonly id: string;
|
}
|
||||||
public readonly name: string;
|
|
||||||
public readonly version: string;
|
|
||||||
public readonly loaders: string[];
|
|
||||||
public readonly dependencies: Dependency[];
|
|
||||||
|
|
||||||
constructor(config: Record<string, unknown>) {
|
const ignoredByDefault = [
|
||||||
super(config);
|
"minecraft",
|
||||||
const root = <Record<string, unknown>>this.config.quilt_loader ?? {};
|
"java",
|
||||||
this.id = String(root.id ?? "");
|
"quilt_loader",
|
||||||
this.name = String(root.name ?? this.id);
|
];
|
||||||
this.version = String(root.version ?? "*");
|
function isDependencyIgnoredByDefault(id: string): boolean {
|
||||||
|
return ignoredByDefault.includes(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultAliases = new Map<string, string | Aliases>([
|
||||||
|
["fabric", "fabric-api"],
|
||||||
|
["quilted_fabric_api", "qsl"],
|
||||||
|
]);
|
||||||
|
function getDefaultDependencyAliases(id: string): Aliases | null {
|
||||||
|
if (id.startsWith("quilted_")) {
|
||||||
|
id = "quilted_fabric_api";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defaultAliases.has(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aliases = defaultAliases.get(id);
|
||||||
|
if (typeof aliases !== "string") {
|
||||||
|
return new Map([...aliases]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Map(PublisherTarget.getValues().map(x => [x, aliases]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProjects(config: QuiltModConfig): Map<PublisherTarget, string> {
|
||||||
|
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];
|
||||||
|
|
||||||
|
if (projectId) {
|
||||||
|
projects.set(target, String(projectId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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<PublisherTarget, string>;
|
||||||
|
|
||||||
|
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.loaders = ["quilt"];
|
||||||
this.dependencies = getDependencyEntries(root.depends)
|
this.dependencies = getDependencies(config);
|
||||||
.concat(getDependencyEntries(root.provides, x => x.embedded = true))
|
this._projects = getProjects(config);
|
||||||
.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);
|
getProjectId(project: PublisherTarget): string | undefined {
|
||||||
|
return this._projects.get(project);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default QuiltModMetadata;
|
||||||
|
|
|
@ -27,35 +27,32 @@
|
||||||
"depends": [
|
"depends": [
|
||||||
{
|
{
|
||||||
"id": "quilt_loader",
|
"id": "quilt_loader",
|
||||||
"version": ">=0.11.3"
|
"versions": ">=0.11.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "quilt_base",
|
"id": "quilt_base",
|
||||||
"version": ">=0.40.0"
|
"versions": ">=0.40.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "minecraft",
|
"id": "minecraft",
|
||||||
"version": "1.17.x"
|
"versions": [
|
||||||
|
"1.17",
|
||||||
|
"1.17.1"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "java",
|
"id": "java",
|
||||||
"version": ">=16"
|
"versions": ">=16"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "recommended-mod",
|
"id": "recommended-mod",
|
||||||
"version": "0.2.0",
|
"versions": "0.2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"mc-publish": {
|
"mc-publish": {
|
||||||
|
"curseforge": 42,
|
||||||
|
"github": "v0.2.0",
|
||||||
"modrinth": "AAAA",
|
"modrinth": "AAAA",
|
||||||
"ignore": true
|
"ignore": true
|
||||||
},
|
|
||||||
"projects": {
|
|
||||||
"curseforge": 42
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"projects": {
|
|
||||||
"github": "v0.2.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -66,22 +63,18 @@
|
||||||
"breaking-mod",
|
"breaking-mod",
|
||||||
{
|
{
|
||||||
"id": "conflicting:conflicting-mod",
|
"id": "conflicting:conflicting-mod",
|
||||||
"version": "<0.40.0",
|
"versions": "<0.40.0",
|
||||||
"unless": "fix-conflicting-mod"
|
"unless": "fix-conflicting-mod"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mc-publish": {
|
"mc-publish": {
|
||||||
|
"github": "mc1.18-0.4.0-alpha5",
|
||||||
"modrinth": "AANobbMI"
|
"modrinth": "AANobbMI"
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"curseforge": 394468
|
"curseforge": 394468
|
||||||
},
|
},
|
||||||
"custom": {
|
|
||||||
"projects": {
|
|
||||||
"github": "mc1.18-0.4.0-alpha5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"example-mod.mixins.json"
|
"example-mod.mixins.json"
|
||||||
],
|
],
|
||||||
|
|
|
@ -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 () => {
|
test("custom metadata can be attached to dependency entry", async () => {
|
||||||
const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!;
|
const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!;
|
||||||
const recommended = metadata.dependencies.find(x => x.id === "recommended-mod")!;
|
const recommended = metadata.dependencies.find(x => x.id === "recommended-mod")!;
|
||||||
|
|
Loading…
Reference in a new issue