Refactoring

This commit is contained in:
Kir_Antipov 2022-07-05 20:44:29 +03:00
parent 03cc643a6e
commit f8343090b1
43 changed files with 302 additions and 276 deletions

View file

@ -32,6 +32,7 @@
"@typescript-eslint/no-empty-function": ["error", { "allow": ["arrowFunctions"] }],
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-vars": ["error", {
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_"

View file

@ -1,10 +1,10 @@
import { getRequiredFiles, gradleOutputSelector } from "./utils/file-utils";
import File, { gradleOutputSelector } from "./utils/io/file";
import PublisherFactory from "./publishing/publisher-factory";
import PublisherTarget from "./publishing/publisher-target";
import { getInputAsObject, mapEnumInput, mapNumberInput } from "./utils/input-utils";
import { getDefaultLogger } from "./utils/logger-utils";
import { retry } from "./utils/function-utils";
import LoggingStopwatch from "./utils/logging-stopwatch";
import { getInputAsObject, mapEnumInput, mapNumberInput } from "./utils/actions/input";
import { getDefaultLogger } from "./utils/logging/logger";
import retry from "./utils/retry";
import LoggingStopwatch from "./utils/logging/logging-stopwatch";
import AggregateError from "aggregate-error";
enum FailMode {
@ -28,8 +28,8 @@ async function main() {
}
const options = { ...commonOptions, ...publisherOptions };
const fileSelector = typeof options.filesPrimary === "string" ? { primary: options.filesPrimary, secondary: typeof options.filesSecondary === "string" ? options.filesSecondary : gradleOutputSelector["secondary"] } : typeof options.files === "string" ? options.files : gradleOutputSelector;
const files = await getRequiredFiles(fileSelector);
const fileSelector = typeof options.filesPrimary === "string" ? { primary: options.filesPrimary, secondary: typeof options.filesSecondary === "string" ? options.filesSecondary : gradleOutputSelector.secondary } : typeof options.files === "string" ? options.files : gradleOutputSelector;
const files = await File.getRequiredFiles(fileSelector);
const retryAttempts = mapNumberInput(options.retryAttempts);
const retryDelay = mapNumberInput(options.retryDelay);
const failMode = mapEnumInput(options.failMode, FailMode, FailMode.Fail as FailMode);

View file

@ -1,7 +1,7 @@
import { File } from "../../utils/file";
import File from "../../utils/io/file";
import ModPublisher from "../mod-publisher";
import PublisherTarget from "../publisher-target";
import { convertToCurseForgeVersions, uploadFile } from "../../utils/curseforge-utils";
import { convertToCurseForgeVersions, uploadFile } from "../../utils/curseforge";
import Dependency from "../../metadata/dependency";
import DependencyKind from "../../metadata/dependency-kind";

View file

@ -1,10 +1,10 @@
import PublisherTarget from "../publisher-target";
import * as github from "@actions/github";
import { File } from "../../utils/file";
import File from "../../utils/io/file";
import ModPublisher from "../../publishing/mod-publisher";
import Dependency from "../../metadata/dependency";
import { mapStringInput, mapBooleanInput } from "../../utils/input-utils";
import { VersionType } from "../../utils/version-utils";
import { mapStringInput, mapBooleanInput } from "../../utils/actions/input";
import VersionType from "../../utils/versioning/version-type";
import { env } from "process";
function getEnvironmentTag(): string | undefined {
@ -49,7 +49,7 @@ export default class GitHubPublisher extends ModPublisher {
}
const generated = !releaseId;
if (generated && (tag ??= environmentTag ?? version)) {
if (!releaseId && (tag ??= environmentTag ?? version)) {
const generateChangelog = mapBooleanInput(options.generateChangelog, !changelog);
const draft = mapBooleanInput(options.draft, false);
const prerelease = mapBooleanInput(options.prerelease, channel !== VersionType.Release);
@ -58,7 +58,7 @@ export default class GitHubPublisher extends ModPublisher {
releaseId = await this.createRelease(tag, name, changelog, generateChangelog, draft, prerelease, commitish, discussion, token);
}
if (!releaseId) {
throw new Error(`Cannot find or create release ${tag || `#${releaseId}`}`);
throw new Error(`Cannot find or create release ${tag}`);
}
const existingAssets = generated ? [] : (await octokit.rest.repos.listReleaseAssets({ ...repo, release_id: releaseId })).data;

View file

@ -1,13 +1,13 @@
import { context } from "@actions/github";
import { parseVersionName, parseVersionNameFromFileVersion } from "../utils/minecraft-utils";
import { File } from "../utils/file";
import { getFiles } from "../utils/file-utils";
import { parseVersionName, parseVersionNameFromFileVersion } from "../utils/minecraft";
import File from "../utils/io/file";
import Publisher from "./publisher";
import PublisherTarget from "./publisher-target";
import MinecraftVersionResolver from "../utils/minecraft-version-resolver";
import MinecraftVersionResolver from "../utils/minecraft/minecraft-version-resolver";
import ModMetadataReader from "../metadata/mod-metadata-reader";
import Dependency from "../metadata/dependency";
import { parseVersionFromName, parseVersionTypeFromName } from "../utils/version-utils";
import Version from "../utils/versioning/version";
import VersionType from "../utils/versioning/version-type";
import DependencyKind from "../metadata/dependency-kind";
import path from "path";
@ -46,7 +46,7 @@ function processDependenciesInput(input: string | string[], inputSplitter?: RegE
}
async function readChangelog(changelogPath: string): Promise<string | never> {
const file = (await getFiles(changelogPath))[0];
const file = (await File.getFiles(changelogPath))[0];
if (!file) {
throw new Error("Changelog file was not found");
}
@ -87,8 +87,8 @@ export default abstract class ModPublisher extends Publisher<ModPublisherOptions
}
const filename = path.parse(files[0].path).name;
const version = (typeof options.version === "string" && options.version) || <string>releaseInfo?.tag_name || metadata?.version || parseVersionFromName(filename);
const versionType = options.versionType?.toLowerCase() || parseVersionTypeFromName(metadata?.version || filename);
const version = (typeof options.version === "string" && options.version) || <string>releaseInfo?.tag_name || metadata?.version || Version.fromName(filename);
const versionType = options.versionType?.toLowerCase() || VersionType.fromName(metadata?.version || filename);
const name = typeof options.name === "string" ? options.name : (<string>releaseInfo?.name || version);
const changelog = typeof options.changelog === "string"
? options.changelog

View file

@ -1,11 +1,11 @@
import { createVersion, getProject, getVersions, modifyVersion } from "../../utils/modrinth-utils";
import { File } from "../../utils/file";
import { createVersion, getProject, getVersions, modifyVersion } from "../../utils/modrinth";
import File from "../../utils/io/file";
import ModPublisher from "../mod-publisher";
import PublisherTarget from "../publisher-target";
import Dependency from "../../metadata/dependency";
import DependencyKind from "../../metadata/dependency-kind";
import { mapBooleanInput, mapEnumInput } from "../../utils/input-utils";
import LoggingStopwatch from "../../utils/logging-stopwatch";
import { mapBooleanInput, mapEnumInput } from "../../utils/actions/input";
import LoggingStopwatch from "../../utils/logging/logging-stopwatch";
enum UnfeatureMode {
None = 0,

View file

@ -3,7 +3,7 @@ import PublisherTarget from "./publisher-target";
import GitHubPublisher from "./github/github-publisher";
import ModrinthPublisher from "./modrinth/modrinth-publisher";
import CurseForgePublisher from "./curseforge/curseforge-publisher";
import Logger from "../utils/logger";
import Logger from "../utils/logging/logger";
export default class PublisherFactory {
public create(target: PublisherTarget, logger?: Logger): Publisher<unknown> {

View file

@ -1,6 +1,6 @@
import { File } from "../utils/file";
import Logger from "../utils/logger";
import { getEmptyLogger } from "../utils/logger-utils";
import File from "../utils/io/file";
import Logger from "../utils/logging/logger";
import { getEmptyLogger } from "../utils/logging/logger";
import PublisherTarget from "./publisher-target";
export default abstract class Publisher<TOptions> {

View file

@ -1,8 +1,8 @@
import fetch from "node-fetch";
import FormData from "form-data";
import { File } from "./file";
import { findVersionByName } from "./minecraft-utils";
import SoftError from "./soft-error";
import File from "../io/file";
import { findVersionByName } from "../minecraft";
import SoftError from "../soft-error";
const baseUrl = "https://minecraft.curseforge.com/api";
@ -36,8 +36,8 @@ class CurseForgeUploadError extends SoftError {
async function fetchJsonArray<T>(url: string): Promise<T[] | never> {
const response = await fetch(url);
if (!response.ok) {
const isServerError = response.status >= 500;
throw new SoftError(isServerError, `${response.status} (${response.statusText})`);
const isSoft = response.status === 429 || response.status >= 500;
throw new SoftError(isSoft, `${response.status} (${response.statusText})`);
}
let array: T[];
@ -145,8 +145,8 @@ export async function uploadFile(id: string, data: Record<string, any>, file: Fi
info = <CurseForgeUploadErrorInfo>await response.json();
errorText += `, ${JSON.stringify(info)}`;
} catch { }
const isServerError = response.status >= 500;
throw new CurseForgeUploadError(isServerError, `Failed to upload file: ${response.status} (${errorText})`, info);
const isSoftError = response.status === 429 || response.status >= 500;
throw new CurseForgeUploadError(isSoftError, `Failed to upload file: ${response.status} (${errorText})`, info);
}
return (<{ id: number }>await response.json()).id;

View file

@ -1,36 +0,0 @@
import glob from "fast-glob";
import { File } from "./file";
export type FileSelector = string | { primary?: string, secondary?: string };
export const gradleOutputSelector: FileSelector = {
primary: "build/libs/!(*-@(dev|sources)).jar",
secondary: "build/libs/*-@(dev|sources).jar"
};
export async function getRequiredFiles(files: FileSelector): Promise<File[] | never> {
const foundFiles = await getFiles(files);
if (foundFiles && foundFiles.length) {
return foundFiles;
}
throw new Error(`Specified files ('${typeof files === "string" ? files : [files.primary, files.secondary].filter(x => x).join(", ")}') were not found`);
}
export async function getFiles(files: FileSelector): Promise<File[]> {
if (!files || typeof files !== "string" && !files.primary && !files.secondary) {
return [];
}
if (typeof files === "string") {
return (await glob(files)).map(x => new File(x));
}
let results = [];
if (files.primary) {
results = (await glob(files.primary)).map(x => new File(x));
}
if (files.secondary) {
results = results.concat((await glob(files.secondary)).map(x => new File(x)));
}
return results.filter((x, i, self) => self.findIndex(y => x.equals(y)) === i);
}

View file

@ -1,33 +0,0 @@
import fs from "fs";
import path from "path";
export class File {
public name: string;
public path: string;
public constructor(filePath: string) {
this.name = path.basename(filePath);
this.path = filePath;
Object.freeze(this);
}
public getStream(): fs.ReadStream {
return fs.createReadStream(this.path);
}
public async getBuffer(): Promise<Buffer> {
return new Promise((resolve, reject) => {
fs.readFile(this.path, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
})
});
}
public equals(file: unknown): boolean {
return file instanceof File && file.path === this.path;
}
}

68
src/utils/io/file.ts Normal file
View file

@ -0,0 +1,68 @@
import fs from "fs";
import path from "path";
import glob from "fast-glob";
export type FileSelector = string | { primary?: string, secondary?: string };
export const gradleOutputSelector = {
primary: "build/libs/!(*-@(dev|sources)).jar",
secondary: "build/libs/*-@(dev|sources).jar"
};
export default class File {
public name: string;
public path: string;
public constructor(filePath: string) {
this.name = path.basename(filePath);
this.path = filePath;
Object.freeze(this);
}
public getStream(): fs.ReadStream {
return fs.createReadStream(this.path);
}
public async getBuffer(): Promise<Buffer> {
return new Promise((resolve, reject) => {
fs.readFile(this.path, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
})
});
}
public equals(file: unknown): boolean {
return file instanceof File && file.path === this.path;
}
public static async getFiles(files: FileSelector): Promise<File[]> {
if (!files || typeof files !== "string" && !files.primary && !files.secondary) {
return [];
}
if (typeof files === "string") {
return (await glob(files)).map(x => new File(x));
}
let results = [];
if (files.primary) {
results = (await glob(files.primary)).map(x => new File(x));
}
if (files.secondary) {
results = results.concat((await glob(files.secondary)).map(x => new File(x)));
}
return results.filter((x, i, self) => self.findIndex(y => x.equals(y)) === i);
}
public static async getRequiredFiles(files: FileSelector): Promise<File[] | never> {
const foundFiles = await File.getFiles(files);
if (foundFiles && foundFiles.length) {
return foundFiles;
}
throw new Error(`Specified files ('${typeof files === "string" ? files : [files.primary, files.secondary].filter(x => x).join(", ")}') were not found`);
}
}

View file

@ -1,7 +0,0 @@
export default interface Logger {
fatal(message: string | Error): void;
error(message: string | Error): void;
warn(message: string | Error): void;
info(message: string | Error): void;
debug(message: string | Error): void;
}

View file

@ -1,6 +1,13 @@
import * as actions from "@actions/core";
import * as console from "console";
import Logger from "./logger";
export default interface Logger {
fatal(message: string | Error): void;
error(message: string | Error): void;
warn(message: string | Error): void;
info(message: string | Error): void;
debug(message: string | Error): void;
}
export function getDefaultLogger(): Logger {
return {

View file

@ -1,5 +1,5 @@
import Logger from "./logger";
import Stopwatch from "./stopwatch";
import Stopwatch from "../diagnostics/stopwatch";
interface StartCallback {
(currentDate: Date, stopwatch: Stopwatch): string;

View file

@ -1,5 +1,5 @@
import fetch from "node-fetch";
import Version from "./version";
import Version from "../versioning/version";
export enum MinecraftVersionType {
Release = "release",

View file

@ -1,6 +1,6 @@
import GameVersionResolver from "./game-version-resolver";
import { getCompatibleBuilds, MinecraftVersion } from "./minecraft-utils";
import Version from "./version";
import GameVersionResolver from "../versioning/game-version-resolver";
import { getCompatibleBuilds, MinecraftVersion } from ".";
import Version from "../versioning/version";
export default class MinecraftVersionResolver extends GameVersionResolver<MinecraftVersion> {
public static readonly exact = new MinecraftVersionResolver((n, v) => [v.find(x => x.version.equals(n))].filter(x => x));

View file

@ -1,8 +1,8 @@
import FormData from "form-data";
import fetch, { Response } from "node-fetch";
import { URLSearchParams } from "url";
import { File } from "./file";
import SoftError from "./soft-error";
import File from "../io/file";
import SoftError from "../soft-error";
const baseUrl = "https://api.modrinth.com/v2";
@ -100,10 +100,10 @@ async function processResponse<T>(response: Response | Promise<Response>, mapper
errorText += `, ${await response.text()}`;
} catch { }
errorText = `${response.status} (${errorText})`;
const isServerError = response.status >= 500;
const isSoftError = response.status === 429 || response.status >= 500;
if (errorFactory) {
throw errorFactory(isServerError, errorText, response);
throw errorFactory(isSoftError, errorText, response);
} else {
throw new SoftError(isServerError, errorText);
throw new SoftError(isSoftError, errorText);
}
}

View file

@ -1,6 +1,6 @@
import sleep from "./sleep";
export async function retry<T>({ func, delay = 0, maxAttempts = -1, softErrorPredicate, errorCallback }: { func: () => T | Promise<T>, delay?: number, maxAttempts?: number, softErrorPredicate?: (error: unknown) => boolean, errorCallback?: (error: unknown) => void }): Promise<T> {
export default async function retry<T>({ func, delay = 0, maxAttempts = -1, softErrorPredicate, errorCallback }: { func: () => T | Promise<T>, delay?: number, maxAttempts?: number, softErrorPredicate?: (error: unknown) => boolean, errorCallback?: (error: unknown) => void }): Promise<T> {
let attempts = 0;
while (true) {
try {

View file

@ -1,20 +0,0 @@
export enum VersionType {
Alpha = "alpha",
Beta = "beta",
Release = "release",
}
export function parseVersionFromName(name: string): string {
const match = name.match(/[a-z]{0,2}\d+\.\d+.*/i);
return match ? match[0] : name;
}
export function parseVersionTypeFromName(name: string): VersionType {
if (name.match(/[+-_]alpha/i)) {
return VersionType.Alpha;
} else if (name.match(/[+-_]beta/i)) {
return VersionType.Beta;
} else {
return VersionType.Release;
}
}

View file

@ -0,0 +1,19 @@
enum VersionType {
Alpha = "alpha",
Beta = "beta",
Release = "release"
}
namespace VersionType {
export function fromName(name: string): VersionType {
if (name.match(/[+-_]alpha/i)) {
return VersionType.Alpha;
} else if (name.match(/[+-_]beta/i)) {
return VersionType.Beta;
} else {
return VersionType.Release;
}
}
}
export default VersionType;

View file

@ -23,4 +23,9 @@ export default class Version {
}
return typeof version === "string" && this.equals(new Version(version));
}
public static fromName(name: string): string {
const match = name.match(/[a-z]{0,2}\d+\.\d+.*/i);
return match ? match[0] : name;
}
}

View file

@ -1,28 +0,0 @@
import { describe, test, expect } from "@jest/globals";
import { getFiles, getRequiredFiles } from "../src/utils/file-utils";
describe("getFiles", () => {
test("all files matching the given pattern are returned", async () => {
expect(await getFiles("(README|LICENSE|FOO).md")).toHaveLength(2);
});
test("files matching the primary pattern are returned first", async () => {
const files = await getFiles({ primary: "README.md", secondary: "(README|LICENSE|FOO).md" });
expect(files).toHaveLength(2);
expect(files[0]).toHaveProperty("name", "README.md");
const inversedFiles = await getFiles({ primary: "LICENSE.md", secondary: "(README|LICENSE|FOO).md" });
expect(inversedFiles).toHaveLength(2);
expect(inversedFiles[0]).toHaveProperty("name", "LICENSE.md");
});
});
describe("getRequiredFiles", () => {
test("all files matching the given pattern are returned", async () => {
expect(await getRequiredFiles("(README|LICENSE|FOO).md")).toHaveLength(2);
});
test("an error is thrown if no files are found", async () => {
await expect(getRequiredFiles("FOO.md")).rejects.toBeInstanceOf(Error);
});
});

View file

@ -1,5 +1,5 @@
import { describe, test, expect } from "@jest/globals";
import DependencyKind from "../src/metadata/dependency-kind";
import DependencyKind from "../../../src/metadata/dependency-kind";
describe("DependencyKind.getValues", () => {
test("all DependencyKind values are returned", () => {

View file

@ -1,6 +1,6 @@
import { describe, test, expect } from "@jest/globals";
import ModLoaderType from "../src/metadata/mod-loader-type";
import ModMetadataReaderFactory from "../src/metadata/mod-metadata-reader-factory";
import ModLoaderType from "../../../src/metadata/mod-loader-type";
import ModMetadataReaderFactory from "../../../src/metadata/mod-metadata-reader-factory";
describe("ModMetadataReaderFactory.create", () => {
test("factory can create metadata reader for every ModLoaderType value", () => {

View file

@ -1,8 +1,8 @@
import { describe, test, expect, beforeAll, afterAll } from "@jest/globals";
import Dependency from "../src/metadata/dependency";
import DependencyKind from "../src/metadata/dependency-kind";
import ModMetadataReader from "../src/metadata/mod-metadata-reader";
import PublisherTarget from "../src/publishing/publisher-target";
import Dependency from "../../../src/metadata/dependency";
import DependencyKind from "../../../src/metadata/dependency-kind";
import ModMetadataReader from "../../../src/metadata/mod-metadata-reader";
import PublisherTarget from "../../../src/publishing/publisher-target";
import { ZipFile } from "yazl";
import fs from "fs";
@ -23,22 +23,22 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("mod info can be read", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.fabric.jar");
const metadata = (await ModMetadataReader.readMetadata("example-mod.fabric.jar"))!;
expect(metadata.id).toBe("example-mod");
expect(metadata.name).toBe("Example Mod");
expect(metadata.version).toBe("0.1.0");
expect(metadata.loaders).toMatchObject(["fabric"]);
expect(metadata.loaders).toMatchObject(["fabric"] as any);
});
test("project ids can be specified in the config file", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.fabric.jar");
const metadata = (await ModMetadataReader.readMetadata("example-mod.fabric.jar"))!;
expect(metadata.getProjectId(PublisherTarget.Modrinth)).toBe("AANobbMI");
expect(metadata.getProjectId(PublisherTarget.CurseForge)).toBe("394468");
expect(metadata.getProjectId(PublisherTarget.GitHub)).toBe("mc1.18-0.4.0-alpha5");
});
test("all dependencies are read", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.fabric.jar");
const metadata = (await ModMetadataReader.readMetadata("example-mod.fabric.jar"))!;
expect(metadata.dependencies).toHaveLength(9);
const dependencies = metadata.dependencies.reduce((agg, x) => { agg[x.id] = x; return agg; }, <Record<string, Dependency>>{});
expect(dependencies["fabricloader"]?.kind).toBe(DependencyKind.Depends);
@ -53,8 +53,8 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("dependency info can be read", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.fabric.jar");
const conflicting = metadata.dependencies.find(x => x.id === "conflicting-mod");
const metadata = (await ModMetadataReader.readMetadata("example-mod.fabric.jar"))!;
const conflicting = metadata.dependencies.find(x => x.id === "conflicting-mod")!;
expect(conflicting).toBeTruthy();
expect(conflicting.id).toBe("conflicting-mod");
expect(conflicting.kind).toBe(DependencyKind.Conflicts);
@ -66,8 +66,8 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("custom metadata can be attached to dependency entry", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.fabric.jar");
const recommended = metadata.dependencies.find(x => x.id === "recommended-mod");
const metadata = (await ModMetadataReader.readMetadata("example-mod.fabric.jar"))!;
const recommended = metadata.dependencies.find(x => x.id === "recommended-mod")!;
expect(recommended).toBeTruthy();
expect(recommended.id).toBe("recommended-mod");
expect(recommended.kind).toBe(DependencyKind.Recommends);
@ -79,15 +79,15 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("special case dependencies (minecraft, java and fabricloader) are ignored by default", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.fabric.jar");
expect(metadata.dependencies.find(x => x.id === "minecraft").ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "java").ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "fabricloader").ignore).toBe(true);
const metadata = (await ModMetadataReader.readMetadata("example-mod.fabric.jar"))!;
expect(metadata.dependencies.find(x => x.id === "minecraft")!.ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "java")!.ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "fabricloader")!.ignore).toBe(true);
});
test("special case dependencies (fabric) are replaced with their aliases", async() => {
const metadata = await ModMetadataReader.readMetadata("example-mod.fabric.jar");
const fabric = metadata.dependencies.find(x => x.id === "fabric");
const metadata = (await ModMetadataReader.readMetadata("example-mod.fabric.jar"))!;
const fabric = metadata.dependencies.find(x => x.id === "fabric")!;
for (const target of PublisherTarget.getValues()) {
expect(fabric.getProjectSlug(target) === "fabric-api");
}
@ -110,22 +110,22 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("mod info can be read", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.forge.jar");
const metadata = (await ModMetadataReader.readMetadata("example-mod.forge.jar"))!;
expect(metadata.id).toBe("example-mod");
expect(metadata.name).toBe("Example Mod");
expect(metadata.version).toBe("0.1.0");
expect(metadata.loaders).toMatchObject(["forge"]);
expect(metadata.loaders).toMatchObject(["forge"] as any);
});
test("project ids can be specified in the config file", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.forge.jar");
const metadata = (await ModMetadataReader.readMetadata("example-mod.forge.jar"))!;
expect(metadata.getProjectId(PublisherTarget.Modrinth)).toBe("AANobbMI");
expect(metadata.getProjectId(PublisherTarget.CurseForge)).toBe("394468");
expect(metadata.getProjectId(PublisherTarget.GitHub)).toBe("mc1.18-0.4.0-alpha5");
});
test("all dependencies are read", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.forge.jar");
const metadata = (await ModMetadataReader.readMetadata("example-mod.forge.jar"))!;
expect(metadata.dependencies).toHaveLength(6);
const dependencies = metadata.dependencies.reduce((agg, x) => { agg[x.id] = x; return agg; }, <Record<string, Dependency>>{});
expect(dependencies["forge"]?.kind).toBe(DependencyKind.Depends);
@ -137,8 +137,8 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("dependency info can be read", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.forge.jar");
const included = metadata.dependencies.find(x => x.id === "included-mod");
const metadata = (await ModMetadataReader.readMetadata("example-mod.forge.jar"))!;
const included = metadata.dependencies.find(x => x.id === "included-mod")!;
expect(included).toBeTruthy();
expect(included.id).toBe("included-mod");
expect(included.kind).toBe(DependencyKind.Includes);
@ -150,8 +150,8 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("custom metadata can be attached to dependency entry", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.forge.jar");
const recommended = metadata.dependencies.find(x => x.id === "recommended-mod");
const metadata = (await ModMetadataReader.readMetadata("example-mod.forge.jar"))!;
const recommended = metadata.dependencies.find(x => x.id === "recommended-mod")!;
expect(recommended).toBeTruthy();
expect(recommended.id).toBe("recommended-mod");
expect(recommended.kind).toBe(DependencyKind.Recommends);
@ -163,10 +163,10 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("special case dependencies (minecraft, java and forge) are ignored by default", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.forge.jar");
expect(metadata.dependencies.find(x => x.id === "minecraft").ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "java").ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "forge").ignore).toBe(true);
const metadata = (await ModMetadataReader.readMetadata("example-mod.forge.jar"))!;
expect(metadata.dependencies.find(x => x.id === "minecraft")!.ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "java")!.ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "forge")!.ignore).toBe(true);
});
});
@ -186,22 +186,22 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("mod info can be read", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.quilt.jar");
const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!;
expect(metadata.id).toBe("example-mod");
expect(metadata.name).toBe("Example Mod");
expect(metadata.version).toBe("0.1.0");
expect(metadata.loaders).toMatchObject(["quilt"]);
expect(metadata.loaders).toMatchObject(["quilt"] as any);
});
test("project ids can be specified in the config file", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.quilt.jar");
const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!;
expect(metadata.getProjectId(PublisherTarget.Modrinth)).toBe("AANobbMI");
expect(metadata.getProjectId(PublisherTarget.CurseForge)).toBe("394468");
expect(metadata.getProjectId(PublisherTarget.GitHub)).toBe("mc1.18-0.4.0-alpha5");
});
test("all dependencies are read", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.quilt.jar");
const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!;
expect(metadata.dependencies).toHaveLength(8);
const dependencies = metadata.dependencies.reduce((agg, x) => { agg[x.id] = x; return agg; }, <Record<string, Dependency>>{});
expect(dependencies["quilt_loader"]?.kind).toBe(DependencyKind.Depends);
@ -215,8 +215,8 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("dependency info can be read", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.quilt.jar");
const conflicting = metadata.dependencies.find(x => x.id === "conflicting-mod");
const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!;
const conflicting = metadata.dependencies.find(x => x.id === "conflicting-mod")!;
expect(conflicting).toBeTruthy();
expect(conflicting.id).toBe("conflicting-mod");
expect(conflicting.kind).toBe(DependencyKind.Conflicts);
@ -228,8 +228,8 @@ describe("ModMetadataReader.readMetadata", () => {
});
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");
const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!;
const recommended = metadata.dependencies.find(x => x.id === "recommended-mod")!;
expect(recommended).toBeTruthy();
expect(recommended.id).toBe("recommended-mod");
expect(recommended.kind).toBe(DependencyKind.Recommends);
@ -241,15 +241,15 @@ describe("ModMetadataReader.readMetadata", () => {
});
test("special case dependencies (minecraft, java and quilt_loader) are ignored by default", async () => {
const metadata = await ModMetadataReader.readMetadata("example-mod.quilt.jar");
expect(metadata.dependencies.find(x => x.id === "minecraft").ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "java").ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "quilt_loader").ignore).toBe(true);
const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!;
expect(metadata.dependencies.find(x => x.id === "minecraft")!.ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "java")!.ignore).toBe(true);
expect(metadata.dependencies.find(x => x.id === "quilt_loader")!.ignore).toBe(true);
});
test("special case dependencies (quilted_quilt_api) are replaced with their aliases", async() => {
const metadata = await ModMetadataReader.readMetadata("example-mod.quilt.jar");
const quilt = metadata.dependencies.find(x => x.id === "quilt_base");
const metadata = (await ModMetadataReader.readMetadata("example-mod.quilt.jar"))!;
const quilt = metadata.dependencies.find(x => x.id === "quilt_base")!;
for (const target of PublisherTarget.getValues()) {
expect(quilt.getProjectSlug(target) === "qsl");
}

View file

@ -1,7 +1,7 @@
import { describe, test, expect } from "@jest/globals";
import PublisherFactory from "../src/publishing/publisher-factory";
import PublisherTarget from "../src/publishing/publisher-target";
import { getConsoleLogger } from "../src/utils/logger-utils";
import PublisherFactory from "../../../src/publishing/publisher-factory";
import PublisherTarget from "../../../src/publishing/publisher-target";
import { getConsoleLogger } from "../../../src/utils/logging/logger";
describe("PublisherFactory.create", () => {
test("factory can create publisher for every PublisherTarget value", () => {
@ -10,7 +10,7 @@ describe("PublisherFactory.create", () => {
const logger = getConsoleLogger();
const publisher = factory.create(target, logger);
expect(publisher.target).toStrictEqual(target);
expect((<any>publisher).logger).toStrictEqual(logger);
expect((publisher as any).logger).toStrictEqual(logger);
}
});
@ -19,7 +19,7 @@ describe("PublisherFactory.create", () => {
for (const target of PublisherTarget.getValues()) {
const publisher = factory.create(target);
expect(publisher.target).toStrictEqual(target);
expect((<any>publisher).logger).toBeTruthy();
expect((publisher as any).logger).toBeTruthy();
}
});

View file

@ -1,6 +1,6 @@
import { describe, test, expect, beforeAll, afterAll } from "@jest/globals";
import { setupInput, unsetInput } from "./utils/input-utils";
import { getInputAsObject, mapStringInput, mapObjectInput, mapNumberInput, mapBooleanInput, mapEnumInput } from "../src/utils/input-utils";
import { setupInput, unsetInput } from "../../../utils/input-utils";
import { getInputAsObject, mapStringInput, mapObjectInput, mapNumberInput, mapBooleanInput, mapEnumInput } from "../../../../src/utils/actions/input";
const defaultInput = {
"boolean": true,
@ -101,10 +101,10 @@ describe("mapObjectInput", () => {
const input = getInputAsObject();
expect(input["boolean"]).not.toBeUndefined();
expect(mapObjectInput(input["boolean"], null)).toBeNull();
expect(mapObjectInput(input["boolean"], null!)).toBeNull();
expect(input["number"]).not.toBeUndefined();
expect(mapObjectInput(input["number"], null)).toBeNull()
expect(mapObjectInput(input["number"], null!)).toBeNull()
expect(input["array"]).not.toBeUndefined();
expect(mapObjectInput(input["array"])).toBeNull()
@ -115,7 +115,7 @@ describe("mapObjectInput", () => {
test("maps object values to object", () => {
const input = getInputAsObject();
expect(mapObjectInput(input["modrinth"], null)).toStrictEqual({ id: "42", token: "1234", filesPrimary: "primaryPath", filesSecondary: "secondaryPath", files: { primary: "primaryPath", secondary: "secondaryPath" } });
expect(mapObjectInput(input["modrinth"], null!)).toStrictEqual({ id: "42", token: "1234", filesPrimary: "primaryPath", filesSecondary: "secondaryPath", files: { primary: "primaryPath", secondary: "secondaryPath" } });
});
});

View file

@ -1,6 +1,6 @@
import process from "process";
import { describe, test, expect } from "@jest/globals";
import { unifyGameVersion, unifyJava, convertToCurseForgeVersions } from "../src/utils/curseforge-utils";
import { unifyGameVersion, unifyJava, convertToCurseForgeVersions } from "../../../../src/utils/curseforge";
describe("unifyGameVersion", () => {
test("versions in the unified format are not changed", async () => {
@ -94,7 +94,7 @@ describe("convertToCurseForgeVersions", () => {
}
};
const curseForgeVersions = await convertToCurseForgeVersions(Object.keys(versions.gameVersions), Object.keys(versions.loaders), Object.keys(versions.java), process.env.CURSEFORGE_TOKEN);
const curseForgeVersions = await convertToCurseForgeVersions(Object.keys(versions.gameVersions), Object.keys(versions.loaders), Object.keys(versions.java), process.env.CURSEFORGE_TOKEN as string);
const expectedIds = new Set([...Object.values<number>(versions.gameVersions), ...Object.values(versions.loaders), ...Object.values(versions.java)]);
expect(curseForgeVersions).toHaveLength(expectedIds.size);

View file

@ -1,6 +1,6 @@
import { describe, test, expect } from "@jest/globals";
import sleep from "../src/utils/sleep";
import Stopwatch from "../src/utils/stopwatch";
import sleep from "../../../../src/utils/sleep";
import Stopwatch from "../../../../src/utils/diagnostics/stopwatch";
describe("Stopwatch", () => {
test("base functionality of Stopwatch works", async () => {

View file

@ -0,0 +1,30 @@
import { describe, test, expect } from "@jest/globals";
import File from "../../../../src/utils/io/file";
describe("File", () => {
describe("getFiles", () => {
test("all files matching the given pattern are returned", async () => {
expect(await File.getFiles("(README|LICENSE|FOO).md")).toHaveLength(2);
});
test("files matching the primary pattern are returned first", async () => {
const files = await File.getFiles({ primary: "README.md", secondary: "(README|LICENSE|FOO).md" });
expect(files).toHaveLength(2);
expect(files[0]).toHaveProperty("name", "README.md");
const inversedFiles = await File.getFiles({ primary: "LICENSE.md", secondary: "(README|LICENSE|FOO).md" });
expect(inversedFiles).toHaveLength(2);
expect(inversedFiles[0]).toHaveProperty("name", "LICENSE.md");
});
});
describe("getRequiredFiles", () => {
test("all files matching the given pattern are returned", async () => {
expect(await File.getRequiredFiles("(README|LICENSE|FOO).md")).toHaveLength(2);
});
test("an error is thrown if no files are found", async () => {
await expect(File.getRequiredFiles("FOO.md")).rejects.toBeInstanceOf(Error);
});
});
});

View file

@ -1,7 +1,7 @@
import { describe, test, expect } from "@jest/globals";
import sleep from "../src/utils/sleep";
import Logger from "../src/utils/logger";
import LogginStopwatch from "../src/utils/logging-stopwatch";
import sleep from "../../../../src/utils/sleep";
import Logger from "../../../../src/utils/logging/logger";
import LogginStopwatch from "../../../../src/utils/logging/logging-stopwatch";
function createLogger(info?: (msg: string) => void): Logger {
const notImplementedLogger = () => {

View file

@ -1,6 +1,6 @@
import { describe, test, expect } from "@jest/globals";
import { getVersionById, findVersionByName, isSnapshot, parseVersionName, parseVersionNameFromFileVersion, getVersions, getLatestRelease, getCompatibleBuilds } from "../src/utils/minecraft-utils";
import Version from "../src/utils/version";
import { getVersionById, findVersionByName, isSnapshot, parseVersionName, parseVersionNameFromFileVersion, getVersions, getLatestRelease, getCompatibleBuilds } from "../../../../src/utils/minecraft";
import Version from "../../../../src/utils/versioning/version";
describe("getVersionById", () => {
test("returned versions have the same id as the given one", async () => {

View file

@ -1,5 +1,5 @@
import { describe, test, expect } from "@jest/globals";
import MinecraftVersionResolver from "../src/utils/minecraft-version-resolver";
import MinecraftVersionResolver from "../../../../src/utils/minecraft/minecraft-version-resolver";
describe("MinecraftVersionResolver.byName", () => {
test("every predefined MinecraftVersionResolver can be resolved", () => {

View file

@ -1,5 +1,5 @@
import { describe, test, expect } from "@jest/globals";
import { getProject, getVersions } from "../src/utils/modrinth-utils";
import { getProject, getVersions } from "../../../../src/utils/modrinth";
const timeout = 15000;
@ -48,12 +48,12 @@ describe("getVersions", () => {
}, timeout);
test("returns only featured versions with featured === true", async () => {
const versions = await getVersions("terra", null, null, true);
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);
const versions = await getVersions("terra", null!, null!, false);
expect(versions.every(x => !x.featured)).toBe(true);
}, timeout);
@ -66,10 +66,10 @@ describe("getVersions", () => {
}, timeout);
test("returns only versions that support given mc versions", async () => {
const versions_1_18_2 = await getVersions("terra", null, ["1.18.2"]);
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"]);
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);
});

View file

@ -1,6 +1,6 @@
import { describe, test, expect } from "@jest/globals";
import { retry } from "../src/utils/function-utils";
import SoftError from "../src/utils/soft-error";
import retry from "../../../src/utils/retry";
import SoftError from "../../../src/utils/soft-error";
function createThrowingFunc(attempts: number): () => true {
let counter = 0;

View file

@ -0,0 +1,14 @@
import { describe, test, expect } from "@jest/globals";
import sleep from "../../../src/utils/sleep";
describe("sleep", () => {
test("execution continues after the specified delay", async () => {
const start = new Date();
await sleep(100);
const end = new Date();
const duration = end.getTime() - start.getTime();
expect(duration).toBeGreaterThan(50);
expect(duration).toBeLessThan(200);
});
});

View file

@ -0,0 +1,18 @@
import { describe, test, expect } from "@jest/globals";
import VersionType from "../../../../src/utils/versioning/version-type";
describe("VersionType", () => {
describe("fromName", () => {
test("version type is correctly extracted from the filename", () => {
expect(VersionType.fromName("sodium-fabric-mc1.17.1-0.3.2+build.7")).toStrictEqual("release");
expect(VersionType.fromName("fabric-api-0.40.1+1.18_experimental")).toStrictEqual("release");
expect(VersionType.fromName("TechReborn-5.0.8-beta+build.111")).toStrictEqual("beta");
expect(VersionType.fromName("TechReborn-1.17-5.0.1-beta+build.29")).toStrictEqual("beta");
expect(VersionType.fromName("Terra-forge-5.3.3-BETA+ec3b0e5d")).toStrictEqual("beta");
expect(VersionType.fromName("Terra-forge-5.3.3-alpha+ec3b0e5d")).toStrictEqual("alpha");
expect(VersionType.fromName("modmenu-2.0.12")).toStrictEqual("release");
expect(VersionType.fromName("enhancedblockentities-0.5+1.17")).toStrictEqual("release");
expect(VersionType.fromName("sync-mc1.17.x-1.2")).toStrictEqual("release");
});
});
});

View file

@ -0,0 +1,17 @@
import { describe, test, expect } from "@jest/globals";
import Version from "../../../../src/utils/versioning/version";
describe("Version", () => {
describe("fromName", () => {
test("file version is correctly extracted from the filename", () => {
expect(Version.fromName("sodium-fabric-mc1.17.1-0.3.2+build.7")).toStrictEqual("mc1.17.1-0.3.2+build.7");
expect(Version.fromName("fabric-api-0.40.1+1.18_experimental")).toStrictEqual("0.40.1+1.18_experimental");
expect(Version.fromName("TechReborn-5.0.8-beta+build.111")).toStrictEqual("5.0.8-beta+build.111");
expect(Version.fromName("TechReborn-1.17-5.0.1-beta+build.29")).toStrictEqual("1.17-5.0.1-beta+build.29");
expect(Version.fromName("Terra-forge-5.3.3-BETA+ec3b0e5d")).toStrictEqual("5.3.3-BETA+ec3b0e5d");
expect(Version.fromName("modmenu-2.0.12")).toStrictEqual("2.0.12");
expect(Version.fromName("enhancedblockentities-0.5+1.17")).toStrictEqual("0.5+1.17");
expect(Version.fromName("sync-mc1.17.x-1.2")).toStrictEqual("mc1.17.x-1.2");
});
});
});

View file

@ -1,29 +0,0 @@
import { describe, test, expect } from "@jest/globals";
import { parseVersionFromName, parseVersionTypeFromName } from "../src/utils/version-utils";
describe("parseVersionFromName", () => {
test("file version is correctly extracted from the filename", () => {
expect(parseVersionFromName("sodium-fabric-mc1.17.1-0.3.2+build.7")).toStrictEqual("mc1.17.1-0.3.2+build.7");
expect(parseVersionFromName("fabric-api-0.40.1+1.18_experimental")).toStrictEqual("0.40.1+1.18_experimental");
expect(parseVersionFromName("TechReborn-5.0.8-beta+build.111")).toStrictEqual("5.0.8-beta+build.111");
expect(parseVersionFromName("TechReborn-1.17-5.0.1-beta+build.29")).toStrictEqual("1.17-5.0.1-beta+build.29");
expect(parseVersionFromName("Terra-forge-5.3.3-BETA+ec3b0e5d")).toStrictEqual("5.3.3-BETA+ec3b0e5d");
expect(parseVersionFromName("modmenu-2.0.12")).toStrictEqual("2.0.12");
expect(parseVersionFromName("enhancedblockentities-0.5+1.17")).toStrictEqual("0.5+1.17");
expect(parseVersionFromName("sync-mc1.17.x-1.2")).toStrictEqual("mc1.17.x-1.2");
});
});
describe("parseVersionTypeFromName", () => {
test("version type is correctly extracted from the filename", () => {
expect(parseVersionTypeFromName("sodium-fabric-mc1.17.1-0.3.2+build.7")).toStrictEqual("release");
expect(parseVersionTypeFromName("fabric-api-0.40.1+1.18_experimental")).toStrictEqual("release");
expect(parseVersionTypeFromName("TechReborn-5.0.8-beta+build.111")).toStrictEqual("beta");
expect(parseVersionTypeFromName("TechReborn-1.17-5.0.1-beta+build.29")).toStrictEqual("beta");
expect(parseVersionTypeFromName("Terra-forge-5.3.3-BETA+ec3b0e5d")).toStrictEqual("beta");
expect(parseVersionTypeFromName("Terra-forge-5.3.3-alpha+ec3b0e5d")).toStrictEqual("alpha");
expect(parseVersionTypeFromName("modmenu-2.0.12")).toStrictEqual("release");
expect(parseVersionTypeFromName("enhancedblockentities-0.5+1.17")).toStrictEqual("release");
expect(parseVersionTypeFromName("sync-mc1.17.x-1.2")).toStrictEqual("release");
});
});