mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-12-04 19:24:46 +01:00
parent
1aaf662cc2
commit
38cc76ee30
5 changed files with 168 additions and 6 deletions
27
README.md
27
README.md
|
@ -16,6 +16,8 @@ jobs:
|
|||
- uses: Kir-Antipov/mc-publish@v3.0
|
||||
with:
|
||||
modrinth-id: AANobbMI
|
||||
modrinth-featured: true
|
||||
modrinth-unfeature-mode: subset
|
||||
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
|
||||
|
||||
curseforge-id: 394468
|
||||
|
@ -91,6 +93,7 @@ jobs:
|
|||
| [modrinth-id](#user-content-modrinth-id) | The ID of the Modrinth project to upload to | A value specified in the config file | `AANobbMI` |
|
||||
| [modrinth-token](#user-content-modrinth-token) | A valid token for the Modrinth API | ❌ | `${{ secrets.MODRINTH_TOKEN }}` |
|
||||
| [modrinth-featured](#user-content-modrinth-featured) | Indicates whether the version should be featured on Modrinth or not | `true` | `true` <br> `false` |
|
||||
| [modrinth-unfeature-mode](#user-content-modrinth-unfeature-mode) | Determines the way automatic unfeaturing of older Modrinth versions works | If [`modrinth-featured`](#user-content-modrinth-featured) is set to true, `subset`; otherwise, `none` | `none` <br> `subset` <br> `intersection` <br> `any` |
|
||||
| [curseforge-id](#user-content-curseforge-id) | The ID of the CurseForge project to upload to | A value specified in the config file | `394468` |
|
||||
| [curseforge-token](#user-content-curseforge-token) | A valid token for the CurseForge API | ❌ | `${{ secrets.CURSEFORGE_TOKEN }}` |
|
||||
| [github-tag](#user-content-github-tag) | The tag name of the release to upload assets to | A tag of the release that triggered the action | `mc1.17.1-0.3.2` |
|
||||
|
@ -227,6 +230,30 @@ Indicates whether the version should be featured on Modrinth or not.
|
|||
modrinth-featured: true
|
||||
```
|
||||
|
||||
#### modrinth-unfeature-mode
|
||||
|
||||
Determines the way automatic unfeaturing of older Modrinth versions works. Default value is `subset`, if [`modrinth-featured`](#user-content-modrinth-featured) is set to true; otherwise, `none`.
|
||||
|
||||
```yaml
|
||||
modrinth-unfeature-mode: version-intersection | loader-subset
|
||||
```
|
||||
|
||||
Available presets:
|
||||
|
||||
- `none` - no Modrinth versions will be unfeatured
|
||||
- `subset` - only those Modrinth versions which are considered a subset of the new one *(i.e., new release suports all of the version's mod loaders **and** game versions)* will be unfeatured
|
||||
- `intersection` - only those Modrinth versions which intersects with the new one *(i.e., support at least one of the mod loaders and one of the game versions supported by the new release)* will be unfeatured
|
||||
- `any` - all Modrinth versions will be unfeatured
|
||||
|
||||
If none of the given presets suits your needs, you can construct a new one from the following values via bitwise `OR`, like so - `version-intersection | loaders-subset`:
|
||||
|
||||
- `version-subset`
|
||||
- `version-intersection`
|
||||
- `version-any`
|
||||
- `loader-subset`
|
||||
- `loader-intersection`
|
||||
- `loader-any`
|
||||
|
||||
#### curseforge-id
|
||||
|
||||
The ID of the CurseForge project to upload to.
|
||||
|
|
|
@ -17,6 +17,10 @@ inputs:
|
|||
description: Indicates whether the version should be featured on Modrinth or not
|
||||
required: false
|
||||
default: ${undefined}
|
||||
modrinth-unfeature-mode: Determines the way automatic unfeaturing of older Modrinth versions works
|
||||
description:
|
||||
required: false
|
||||
default: ${undefined}
|
||||
|
||||
curseforge-id:
|
||||
description: The ID of the CurseForge project to upload to
|
||||
|
|
|
@ -1,10 +1,30 @@
|
|||
import { createVersion, getProject } from "../../utils/modrinth-utils";
|
||||
import { createVersion, getProject, getVersions, modifyVersion } from "../../utils/modrinth-utils";
|
||||
import { File } from "../../utils/file";
|
||||
import ModPublisher from "../mod-publisher";
|
||||
import PublisherTarget from "../publisher-target";
|
||||
import Dependency from "../../metadata/dependency";
|
||||
import DependencyKind from "../../metadata/dependency-kind";
|
||||
import { mapBooleanInput } from "../../utils/input-utils";
|
||||
import { mapBooleanInput, mapEnumInput } from "../../utils/input-utils";
|
||||
|
||||
enum UnfeatureMode {
|
||||
None = 0,
|
||||
|
||||
VersionSubset = 1,
|
||||
VersionIntersection = 2,
|
||||
VersionAny = 4,
|
||||
|
||||
LoaderSubset = 8,
|
||||
LoaderIntersection = 16,
|
||||
LoaderAny = 32,
|
||||
|
||||
Subset = VersionSubset | LoaderSubset,
|
||||
Intersection = VersionIntersection | LoaderIntersection,
|
||||
Any = VersionAny | LoaderAny,
|
||||
}
|
||||
|
||||
function hasFlag(unfeatureMode: UnfeatureMode, flag: UnfeatureMode): boolean {
|
||||
return (unfeatureMode & flag) === flag;
|
||||
}
|
||||
|
||||
const modrinthDependencyKinds = new Map([
|
||||
[DependencyKind.Depends, "required"],
|
||||
|
@ -21,6 +41,7 @@ export default class ModrinthPublisher extends ModPublisher {
|
|||
|
||||
protected async publishMod(id: string, token: string, name: string, version: string, channel: string, loaders: string[], gameVersions: string[], _java: string[], changelog: string, files: File[], dependencies: Dependency[], options: Record<string, unknown>): Promise<void> {
|
||||
const featured = mapBooleanInput(options.featured, true);
|
||||
const unfeatureMode = mapEnumInput(options.unfeatureMode, UnfeatureMode, featured ? UnfeatureMode.Subset : UnfeatureMode.None);
|
||||
const projects = (await Promise.all(dependencies
|
||||
.filter((x, _, self) => (x.kind !== DependencyKind.Suggests && x.kind !== DependencyKind.Includes) || !self.find(y => y.id === x.id && y.kind !== DependencyKind.Suggests && y.kind !== DependencyKind.Includes))
|
||||
.map(async x => ({
|
||||
|
@ -29,6 +50,10 @@ export default class ModrinthPublisher extends ModPublisher {
|
|||
}))))
|
||||
.filter(x => x.project_id && x.dependency_type);
|
||||
|
||||
if (unfeatureMode !== UnfeatureMode.None) {
|
||||
await this.unfeatureOlderVersions(id, token, unfeatureMode, loaders, gameVersions);
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: name || version,
|
||||
version_number: version,
|
||||
|
@ -41,4 +66,36 @@ export default class ModrinthPublisher extends ModPublisher {
|
|||
};
|
||||
await createVersion(id, data, files, token);
|
||||
}
|
||||
|
||||
private async unfeatureOlderVersions(id: string, token: string, unfeatureMode: UnfeatureMode, loaders: string[], gameVersions: string[]): Promise<void> {
|
||||
this.logger.info("Unfeaturing older Modrinth versions...");
|
||||
const start = new Date();
|
||||
const unfeaturedVersions = <string[]>[];
|
||||
|
||||
const versionSubset = hasFlag(unfeatureMode, UnfeatureMode.VersionSubset);
|
||||
const loaderSubset = hasFlag(unfeatureMode, UnfeatureMode.LoaderSubset);
|
||||
const olderVersions = await getVersions(id, hasFlag(unfeatureMode, UnfeatureMode.LoaderAny) ? null : loaders, hasFlag(unfeatureMode, UnfeatureMode.VersionAny) ? null : gameVersions, true, token);
|
||||
for (const olderVersion of olderVersions) {
|
||||
if (loaderSubset && !olderVersion.loaders.every(x => loaders.includes(x))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (versionSubset && !olderVersion.game_versions.every(x => gameVersions.includes(x))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await modifyVersion(olderVersion.id, { featured: false }, token)) {
|
||||
unfeaturedVersions.push(olderVersion.id);
|
||||
} else {
|
||||
this.logger.warn(`Cannot unfeature version ${olderVersion.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (unfeaturedVersions.length) {
|
||||
const end = new Date();
|
||||
this.logger.info(`Successfully unfeatured versions ${unfeaturedVersions.join(", ")} (in ${end.getTime() - start.getTime()} ms)`);
|
||||
} else {
|
||||
this.logger.info("No versions to unfeature were found");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import FormData from "form-data";
|
||||
import fetch, { Response } from "node-fetch";
|
||||
import { URLSearchParams } from "url";
|
||||
import { File } from "./file";
|
||||
import SoftError from "./soft-error";
|
||||
|
||||
|
@ -12,6 +13,9 @@ interface ModrinthProject {
|
|||
|
||||
interface ModrinthVersion {
|
||||
id: string;
|
||||
loaders: string[];
|
||||
game_versions: string[];
|
||||
featured: boolean;
|
||||
}
|
||||
|
||||
export function createVersion(modId: string, data: Record<string, any>, files: File[], token: string): Promise<ModrinthVersion> {
|
||||
|
@ -44,7 +48,37 @@ export function createVersion(modId: string, data: Record<string, any>, files: F
|
|||
|
||||
export function getProject(idOrSlug: string): Promise<ModrinthProject> {
|
||||
return processResponse(fetch(`${baseUrl}/project/${idOrSlug}`), { 404: () => <ModrinthProject>null });
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
export function getVersions(idOrSlug: string, loaders?: string[], gameVersions?: string[], featured?: boolean, token?: string): Promise<ModrinthVersion[]> {
|
||||
const urlParams = new URLSearchParams();
|
||||
if (loaders) {
|
||||
urlParams.append("loaders", JSON.stringify(loaders));
|
||||
}
|
||||
if (gameVersions) {
|
||||
urlParams.append("game_versions", JSON.stringify(gameVersions));
|
||||
}
|
||||
if (typeof featured === "boolean") {
|
||||
urlParams.append("featured", String(featured));
|
||||
}
|
||||
|
||||
const response = fetch(`${baseUrl}/project/${idOrSlug}/version?${urlParams}`, token ? {
|
||||
headers: { "Authorization": token }
|
||||
} : undefined);
|
||||
return processResponse(response, { 404: () => <ModrinthVersion[]>[] });
|
||||
}
|
||||
|
||||
export async function modifyVersion(id: string, version: Partial<ModrinthVersion>, token: string): Promise<boolean> {
|
||||
const response = await fetch(`${baseUrl}/version/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Authorization": token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(version)
|
||||
});
|
||||
|
||||
return response.ok;
|
||||
}
|
||||
|
||||
async function processResponse<T>(response: Response | Promise<Response>, mappers?: Record<number, (response: Response) => T | Promise<T>>, errorFactory?: (isServerError: boolean, message: string, response: Response) => Error | Promise<Error>): Promise<T | never> {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { describe, test, expect } from "@jest/globals";
|
||||
import { getProject } from "../src/utils/modrinth-utils";
|
||||
import { getProject, getVersions } from "../src/utils/modrinth-utils";
|
||||
|
||||
const timeout = 15000;
|
||||
|
||||
describe("getProject", () => {
|
||||
test("returned versions have expected ids", async () => {
|
||||
|
@ -15,7 +17,7 @@ describe("getProject", () => {
|
|||
const project = await getProject(slug);
|
||||
expect(project).toHaveProperty("id", id);
|
||||
}
|
||||
}, 15000);
|
||||
}, timeout);
|
||||
|
||||
test("the method returns null if project with the given slug does not exist", async () => {
|
||||
const nonExistentProjects = [
|
||||
|
@ -31,5 +33,43 @@ describe("getProject", () => {
|
|||
const project = await getProject(slug);
|
||||
expect(project).toBeNull();
|
||||
}
|
||||
}, 15000);
|
||||
}, timeout);
|
||||
});
|
||||
|
||||
describe("getVersions", () => {
|
||||
test("returns unfiltered versions if no parameters were passed", async () => {
|
||||
const versions = await getVersions("terra");
|
||||
expect(versions.find(x => x.featured)).toBeTruthy();
|
||||
expect(versions.find(x => !x.featured)).toBeTruthy();
|
||||
expect(versions.find(x => x.loaders.includes("fabric"))).toBeTruthy();
|
||||
expect(versions.find(x => x.loaders.includes("forge"))).toBeTruthy();
|
||||
expect(versions.find(x => x.game_versions.includes("1.18.2"))).toBeTruthy();
|
||||
expect(versions.find(x => x.game_versions.includes("1.16.5"))).toBeTruthy();
|
||||
}, timeout);
|
||||
|
||||
test("returns only featured versions with featured === true", async () => {
|
||||
const versions = await getVersions("terra", null, null, true);
|
||||
expect(versions.every(x => x.featured)).toBe(true);
|
||||
}, timeout);
|
||||
|
||||
test("returns only unfeatured versions with featured === false", async () => {
|
||||
const versions = await getVersions("terra", null, null, false);
|
||||
expect(versions.every(x => !x.featured)).toBe(true);
|
||||
}, timeout);
|
||||
|
||||
test("returns only versions that support given modloaders", async () => {
|
||||
const fabricVersions = await getVersions("terra", ["fabric"]);
|
||||
expect(fabricVersions.every(x => x.loaders.includes("fabric"))).toBe(true);
|
||||
|
||||
const forgeVersions = await getVersions("terra", ["forge"]);
|
||||
expect(forgeVersions.every(x => x.loaders.includes("forge"))).toBe(true);
|
||||
}, timeout);
|
||||
|
||||
test("returns only versions that support given mc versions", async () => {
|
||||
const versions_1_18_2 = await getVersions("terra", null, ["1.18.2"]);
|
||||
expect(versions_1_18_2.every(x => x.game_versions.includes("1.18.2"))).toBe(true);
|
||||
|
||||
const versions_1_16_5 = await getVersions("terra", null, ["1.16.5"]);
|
||||
expect(versions_1_16_5.every(x => x.game_versions.includes("1.16.5"))).toBe(true);
|
||||
}, timeout);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue