mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-28 19:31:03 -05:00
Refactoring: made options
an argument instead of a property
This commit is contained in:
parent
e000a2ff38
commit
bc45e09dbd
8 changed files with 69 additions and 81 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import { getRequiredFiles, gradleOutputSelector } from "./utils/file-utils";
|
||||||
import PublisherFactory from "./publishing/publisher-factory";
|
import PublisherFactory from "./publishing/publisher-factory";
|
||||||
import PublisherTarget from "./publishing/publisher-target";
|
import PublisherTarget from "./publishing/publisher-target";
|
||||||
import { getInputAsObject } from "./utils/input-utils";
|
import { getInputAsObject } from "./utils/input-utils";
|
||||||
|
@ -16,10 +17,14 @@ async function main() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const publisher = publisherFactory.create(target, { ...commonOptions, ...publisherOptions }, logger);
|
const options = { ...commonOptions, ...publisherOptions };
|
||||||
|
const fileSelector = options.files && (typeof(options.files) === "string" || options.files.primary) ? options.files : gradleOutputSelector;
|
||||||
|
const files = await getRequiredFiles(fileSelector);
|
||||||
|
|
||||||
|
const publisher = publisherFactory.create(target, logger);
|
||||||
logger.info(`Publishing assets to ${targetName}...`);
|
logger.info(`Publishing assets to ${targetName}...`);
|
||||||
const start = new Date();
|
const start = new Date();
|
||||||
await publisher.publish();
|
await publisher.publish(files, options);
|
||||||
logger.info(`Successfully published assets to ${targetName} (in ${new Date().getTime() - start.getTime()}ms)`);
|
logger.info(`Successfully published assets to ${targetName} (in ${new Date().getTime() - start.getTime()}ms)`);
|
||||||
publishedTo.push(targetName);
|
publishedTo.push(targetName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Publisher from "./publisher";
|
import Publisher from "./publisher";
|
||||||
import PublisherTarget from "./publisher-target";
|
import PublisherTarget from "./publisher-target";
|
||||||
import * as github from "@actions/github";
|
import * as github from "@actions/github";
|
||||||
import { getFiles } from "../utils/file-utils";
|
import { File } from "../utils/file-utils";
|
||||||
|
|
||||||
interface GitHubPublisherOptions {
|
interface GitHubPublisherOptions {
|
||||||
tag?: string;
|
tag?: string;
|
||||||
|
@ -9,22 +9,18 @@ interface GitHubPublisherOptions {
|
||||||
files?: string | { primary?: string, secondary?: string };
|
files?: string | { primary?: string, secondary?: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultFiles = {
|
|
||||||
primary: "build/libs/!(*-@(dev|sources)).jar",
|
|
||||||
secondary: "build/libs/*-@(dev|sources).jar"
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class GitHubPublisher extends Publisher<GitHubPublisherOptions> {
|
export default class GitHubPublisher extends Publisher<GitHubPublisherOptions> {
|
||||||
public get target(): PublisherTarget {
|
public get target(): PublisherTarget {
|
||||||
return PublisherTarget.GitHub;
|
return PublisherTarget.GitHub;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async publish(): Promise<void> {
|
public async publish(files: File[], options: GitHubPublisherOptions): Promise<void> {
|
||||||
|
this.validateOptions(options);
|
||||||
let releaseId = 0;
|
let releaseId = 0;
|
||||||
const repo = github.context.repo;
|
const repo = github.context.repo;
|
||||||
const octokit = github.getOctokit(this.options.token);
|
const octokit = github.getOctokit(options.token);
|
||||||
if (this.options.tag) {
|
if (options.tag) {
|
||||||
const response = await octokit.rest.repos.getReleaseByTag({ ...repo, tag: this.options.tag });
|
const response = await octokit.rest.repos.getReleaseByTag({ ...repo, tag: options.tag });
|
||||||
if (response.status >= 200 && response.status < 300) {
|
if (response.status >= 200 && response.status < 300) {
|
||||||
releaseId = response.data.id;
|
releaseId = response.data.id;
|
||||||
}
|
}
|
||||||
|
@ -32,13 +28,7 @@ export default class GitHubPublisher extends Publisher<GitHubPublisherOptions> {
|
||||||
releaseId = github.context.payload.release?.id;
|
releaseId = github.context.payload.release?.id;
|
||||||
}
|
}
|
||||||
if (!releaseId) {
|
if (!releaseId) {
|
||||||
throw new Error(`Couldn't find release #${this.options.tag || releaseId}`);
|
throw new Error(`Couldn't find release #${options.tag || releaseId}`);
|
||||||
}
|
|
||||||
|
|
||||||
const fileSelector = this.options.files && (typeof(this.options.files) === "string" || this.options.files.primary) ? this.options.files : defaultFiles;
|
|
||||||
const files = await getFiles(fileSelector);
|
|
||||||
if (!files.length) {
|
|
||||||
throw new Error(`Specified files (${typeof fileSelector === "string" ? fileSelector : fileSelector.primary}) were not found`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingAssets = (await octokit.rest.repos.listReleaseAssets({ ...repo, release_id: releaseId })).data;
|
const existingAssets = (await octokit.rest.repos.listReleaseAssets({ ...repo, release_id: releaseId })).data;
|
||||||
|
|
|
@ -17,11 +17,6 @@ interface ModPublisherOptions {
|
||||||
files?: string | { primary?: string, secondary?: string };
|
files?: string | { primary?: string, secondary?: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultFiles = {
|
|
||||||
primary: "build/libs/!(*-@(dev|sources)).jar",
|
|
||||||
secondary: "build/libs/*-@(dev|sources).jar"
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultLoaders = ["fabric"];
|
const defaultLoaders = ["fabric"];
|
||||||
|
|
||||||
function processMultilineInput(input: string | string[], splitter?: RegExp): string[] {
|
function processMultilineInput(input: string | string[], splitter?: RegExp): string[] {
|
||||||
|
@ -43,37 +38,32 @@ async function readChangelog(changelog: string | { file?: string }): Promise<str
|
||||||
return (await file.getBuffer()).toString("utf8");
|
return (await file.getBuffer()).toString("utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
export default abstract class ModPublisher<TUniqueOptions = unknown> extends Publisher<ModPublisherOptions & TUniqueOptions> {
|
export default abstract class ModPublisher extends Publisher<ModPublisherOptions> {
|
||||||
public async publish(): Promise<void> {
|
public async publish(files: File[], options: ModPublisherOptions): Promise<void> {
|
||||||
|
this.validateOptions(options);
|
||||||
const releaseInfo = <any>context.payload.release;
|
const releaseInfo = <any>context.payload.release;
|
||||||
|
|
||||||
const id = this.options.id;
|
const id = options.id;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error(`Project id is required to publish your assets to ${PublisherTarget.toString(this.target)}`);
|
throw new Error(`Project id is required to publish your assets to ${PublisherTarget.toString(this.target)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = this.options.token;
|
const token = options.token;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error(`Token is required to publish your assets to ${PublisherTarget.toString(this.target)}`);
|
throw new Error(`Token is required to publish your assets to ${PublisherTarget.toString(this.target)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileSelector = this.options.files && (typeof(this.options.files) === "string" || this.options.files.primary) ? this.options.files : defaultFiles;
|
const version = (typeof options.version === "string" && options.version) || <string>releaseInfo?.tag_name || parseVersionFromFilename(files[0].name);
|
||||||
const files = await getFiles(fileSelector);
|
const versionType = options.versionType?.toLowerCase() || parseVersionTypeFromFilename(files[0].name);
|
||||||
if (!files.length) {
|
const name = (typeof options.name === "string" && options.name) || <string>releaseInfo?.name || version;
|
||||||
throw new Error(`Specified files (${typeof fileSelector === "string" ? fileSelector : fileSelector.primary}) were not found`);
|
const changelog = ((typeof options.changelog === "string" || options.changelog?.file) ? (await readChangelog(options.changelog)) : <string>releaseInfo?.body) || "";
|
||||||
}
|
|
||||||
|
|
||||||
const version = (typeof this.options.version === "string" && this.options.version) || <string>releaseInfo?.tag_name || parseVersionFromFilename(files[0].name);
|
const loaders = processMultilineInput(options.loaders, /\s+/);
|
||||||
const versionType = this.options.versionType?.toLowerCase() || parseVersionTypeFromFilename(files[0].name);
|
|
||||||
const name = (typeof this.options.name === "string" && this.options.name) || <string>releaseInfo?.name || version;
|
|
||||||
const changelog = ((typeof this.options.changelog === "string" || this.options.changelog?.file) ? (await readChangelog(this.options.changelog)) : <string>releaseInfo?.body) || "";
|
|
||||||
|
|
||||||
const loaders = processMultilineInput(this.options.loaders, /\s+/);
|
|
||||||
if (!loaders.length) {
|
if (!loaders.length) {
|
||||||
loaders.push(...defaultLoaders);
|
loaders.push(...defaultLoaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
const gameVersions = processMultilineInput(this.options.gameVersions);
|
const gameVersions = processMultilineInput(options.gameVersions);
|
||||||
if (!gameVersions.length) {
|
if (!gameVersions.length) {
|
||||||
const minecraftVersion = parseVersionNameFromFileVersion(version);
|
const minecraftVersion = parseVersionNameFromFileVersion(version);
|
||||||
if (minecraftVersion) {
|
if (minecraftVersion) {
|
||||||
|
@ -84,7 +74,7 @@ export default abstract class ModPublisher<TUniqueOptions = unknown> extends Pub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const java = processMultilineInput(this.options.java);
|
const java = processMultilineInput(options.java);
|
||||||
|
|
||||||
await this.publishMod(id, token, name, version, versionType, loaders, gameVersions, java, changelog, files);
|
await this.publishMod(id, token, name, version, versionType, loaders, gameVersions, java, changelog, files);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,16 @@ import CurseForgePublisher from "./curseforge-publisher";
|
||||||
import Logger from "../utils/logger";
|
import Logger from "../utils/logger";
|
||||||
|
|
||||||
export default class PublisherFactory {
|
export default class PublisherFactory {
|
||||||
public create(target: PublisherTarget, options: Record<string, unknown>, logger?: Logger): Publisher<unknown> {
|
public create(target: PublisherTarget, logger?: Logger): Publisher<unknown> {
|
||||||
switch(target) {
|
switch(target) {
|
||||||
case PublisherTarget.GitHub:
|
case PublisherTarget.GitHub:
|
||||||
return new GitHubPublisher(<any>options, logger);
|
return new GitHubPublisher(logger);
|
||||||
|
|
||||||
case PublisherTarget.Modrinth:
|
case PublisherTarget.Modrinth:
|
||||||
return new ModrinthPublisher(<any>options, logger);
|
return new ModrinthPublisher(logger);
|
||||||
|
|
||||||
case PublisherTarget.CurseForge:
|
case PublisherTarget.CurseForge:
|
||||||
return new CurseForgePublisher(<any>options, logger);
|
return new CurseForgePublisher(logger);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown target "${PublisherTarget.toString(target)}"`);
|
throw new Error(`Unknown target "${PublisherTarget.toString(target)}"`);
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
|
import { File } from "utils/file-utils";
|
||||||
import Logger from "../utils/logger";
|
import Logger from "../utils/logger";
|
||||||
import { getEmptyLogger } from "../utils/logger-utils";
|
import { getEmptyLogger } from "../utils/logger-utils";
|
||||||
import PublisherTarget from "./publisher-target";
|
import PublisherTarget from "./publisher-target";
|
||||||
|
|
||||||
export default abstract class Publisher<TOptions> {
|
export default abstract class Publisher<TOptions> {
|
||||||
protected readonly options: TOptions;
|
|
||||||
protected readonly logger: Logger;
|
protected readonly logger: Logger;
|
||||||
|
|
||||||
public constructor(options: TOptions, logger?: Logger) {
|
public constructor(logger?: Logger) {
|
||||||
if (!options || typeof options !== "object") {
|
|
||||||
throw new Error(`Expected options to be an object, got ${options ? typeof options : options}`);
|
|
||||||
}
|
|
||||||
this.options = options;
|
|
||||||
this.logger = logger || getEmptyLogger();
|
this.logger = logger || getEmptyLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract get target(): PublisherTarget;
|
public abstract get target(): PublisherTarget;
|
||||||
|
|
||||||
public abstract publish(): Promise<void>;
|
public abstract publish(files: File[], options: TOptions): Promise<void>;
|
||||||
|
|
||||||
|
protected validateOptions(options: TOptions): void | never {
|
||||||
|
if (!options || typeof options !== "object") {
|
||||||
|
throw new Error(`Expected options to be an object, got ${options ? typeof options : options}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,22 +24,27 @@ export class File {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStats(): Promise<fs.Stats> {
|
|
||||||
return new Promise((resolve, reject) => fs.stat(this.path, (error, stats) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
resolve(stats);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public equals(file: unknown): boolean {
|
public equals(file: unknown): boolean {
|
||||||
return file instanceof File && file.path === this.path;
|
return file instanceof File && file.path === this.path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFiles(files: string | { primary?: string, secondary?: string }): Promise<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) {
|
if (!files || typeof files !== "string" && !files.primary && !files.secondary) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, test, expect } from "@jest/globals";
|
import { describe, test, expect } from "@jest/globals";
|
||||||
import { getFiles, parseVersionFromFilename, parseVersionTypeFromFilename } from "../src/utils/file-utils";
|
import { getFiles, getRequiredFiles, parseVersionFromFilename, parseVersionTypeFromFilename } from "../src/utils/file-utils";
|
||||||
|
|
||||||
describe("getFiles", () => {
|
describe("getFiles", () => {
|
||||||
test("all files matching the given pattern are returned", async () => {
|
test("all files matching the given pattern are returned", async () => {
|
||||||
|
@ -17,6 +17,16 @@ describe("getFiles", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("parseVersionFromFilename", () => {
|
describe("parseVersionFromFilename", () => {
|
||||||
test("file version is correctly extracted from the filename", () => {
|
test("file version is correctly extracted from the filename", () => {
|
||||||
expect(parseVersionFromFilename("sodium-fabric-mc1.17.1-0.3.2+build.7.jar")).toStrictEqual("mc1.17.1-0.3.2+build.7");
|
expect(parseVersionFromFilename("sodium-fabric-mc1.17.1-0.3.2+build.7.jar")).toStrictEqual("mc1.17.1-0.3.2+build.7");
|
||||||
|
|
|
@ -7,11 +7,9 @@ describe("PublisherFactory.create", () => {
|
||||||
test("factory can create publisher for every PublisherTarget value", () => {
|
test("factory can create publisher for every PublisherTarget value", () => {
|
||||||
const factory = new PublisherFactory();
|
const factory = new PublisherFactory();
|
||||||
for (const target of PublisherTarget.getValues()) {
|
for (const target of PublisherTarget.getValues()) {
|
||||||
const options = {};
|
|
||||||
const logger = getConsoleLogger();
|
const logger = getConsoleLogger();
|
||||||
const publisher = factory.create(target, options, logger);
|
const publisher = factory.create(target, logger);
|
||||||
expect(publisher.target).toStrictEqual(target);
|
expect(publisher.target).toStrictEqual(target);
|
||||||
expect((<any>publisher).options).toStrictEqual(options);
|
|
||||||
expect((<any>publisher).logger).toStrictEqual(logger);
|
expect((<any>publisher).logger).toStrictEqual(logger);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -19,26 +17,14 @@ describe("PublisherFactory.create", () => {
|
||||||
test("every publisher has logger object", () => {
|
test("every publisher has logger object", () => {
|
||||||
const factory = new PublisherFactory();
|
const factory = new PublisherFactory();
|
||||||
for (const target of PublisherTarget.getValues()) {
|
for (const target of PublisherTarget.getValues()) {
|
||||||
const options = {};
|
const publisher = factory.create(target);
|
||||||
const publisher = factory.create(target, options);
|
|
||||||
expect(publisher.target).toStrictEqual(target);
|
expect(publisher.target).toStrictEqual(target);
|
||||||
expect((<any>publisher).options).toStrictEqual(options);
|
|
||||||
expect((<any>publisher).logger).toBeTruthy();
|
expect((<any>publisher).logger).toBeTruthy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("the method throws on invalid PublisherTarget value", () => {
|
test("the method throws on invalid PublisherTarget value", () => {
|
||||||
const factory = new PublisherFactory();
|
const factory = new PublisherFactory();
|
||||||
expect(() => factory.create(-1, {})).toThrow();
|
expect(() => factory.create(-1)).toThrow();
|
||||||
});
|
|
||||||
|
|
||||||
test("the method throws on invalid options", () => {
|
|
||||||
const factory = new PublisherFactory();
|
|
||||||
const invalidOptions = [null, undefined, "", true, false, () => {}];
|
|
||||||
for (const target of PublisherTarget.getValues()) {
|
|
||||||
for (const options of invalidOptions) {
|
|
||||||
expect(() => factory.create(target, <any>options)).toThrow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue