Refactored GitHubPublisher (-> GitHubUploader)

This commit is contained in:
Kir_Antipov 2023-04-22 13:46:18 +00:00
parent 806f2f658c
commit 49cd72c033
2 changed files with 136 additions and 115 deletions

View file

@ -0,0 +1,136 @@
import { GitHubUploadRequest as UploadRequest, GitHubUploadReport as UploadReport } from "@/action";
import { GenericPlatformUploader, GenericPlatformUploaderOptions } from "@/platforms/generic-platform-uploader";
import { PlatformType } from "@/platforms/platform-type";
import { GitHubContext } from "./github-context";
import { ArgumentNullError } from "@/utils/errors";
import { GitHubApiClient } from "./github-api-client";
import { GitHubRelease } from "./github-release";
/**
* Configuration options for the uploader, tailored for use with GitHub.
*/
export interface GitHubUploaderOptions extends GenericPlatformUploaderOptions {
/**
* Provides the context of the current GitHub Actions workflow run.
*/
githubContext: GitHubContext;
}
/**
* Defines the structure for an upload request, adapted for use with GitHub.
*/
export type GitHubUploadRequest = UploadRequest;
/**
* Specifies the structure of the report generated after a successful upload to GitHub.
*/
export type GitHubUploadReport = UploadReport;
/**
* Implements the uploader for GitHub.
*/
export class GitHubUploader extends GenericPlatformUploader<GitHubUploaderOptions, GitHubUploadRequest, GitHubUploadReport> {
/**
* Provides the context of the current GitHub Actions workflow run.
*/
private readonly _context: GitHubContext;
/**
* Constructs a new {@link GitHubUploader} instance.
*
* @param options - The options to use for the uploader.
*/
constructor(options: GitHubUploaderOptions) {
ArgumentNullError.throwIfNull(options, "options");
ArgumentNullError.throwIfNull(options.githubContext, "options.githubContext");
ArgumentNullError.throwIfNull(options.githubContext.repo, "options.githubContext.repo");
super(options);
this._context = options.githubContext;
}
/**
* @inheritdoc
*/
get platform(): PlatformType {
return PlatformType.GITHUB;
}
/**
* @inheritdoc
*/
protected async uploadCore(request: GitHubUploadRequest): Promise<GitHubUploadReport> {
const api = new GitHubApiClient({ token: request.token.unwrap(), baseUrl: this._context.apiUrl });
const repo = this._context.repo;
const releaseId = await this.getOrCreateReleaseId(request, api);
const release = await this.updateRelease(request, releaseId, api);
return {
repo: `${repo.owner}/${repo.repo}`,
tag: release.tag_name,
url: release.html_url,
files: release.assets.map(x => ({ id: x.id, name: x.name, url: x.url })),
};
}
/**
* Retrieves the ID of an existing release that matches the request parameters.
* If no such release exists, it creates a new release and returns its ID.
*
* @param request - Contains parameters that define the desired release.
* @param api - An instance of the GitHub API client for interacting with GitHub services.
*
* @returns The ID of the release corresponding to the request parameters.
*/
private async getOrCreateReleaseId(request: GitHubUploadRequest, api: GitHubApiClient): Promise<number> {
const repo = this._context.repo;
const tag = request.tag || this._context.tag || request.version;
let releaseId = undefined as number;
if (request.tag) {
releaseId = await api.getRelease({ ...repo, tag_name: request.tag }).then(x => x?.id);
} else if (this._context.payload.release?.id) {
releaseId = this._context.payload.release.id;
} else if (tag) {
releaseId = await api.getRelease({ ...repo, tag_name: tag }).then(x => x?.id);
}
if (!releaseId && tag) {
releaseId = (await api.createRelease({
...repo,
tag_name: tag,
target_commitish: request.commitish,
name: request.name,
body: request.changelog,
draft: request.draft,
prerelease: request.prerelease,
discussion_category_name: request.discussion,
generate_release_notes: request.generateChangelog,
}))?.id;
}
if (!releaseId) {
throw new Error(`Cannot find or create GitHub Release${tag ? ` (${tag})` : ""}.`);
}
return releaseId;
}
/**
* Updates the content of an existing GitHub release based on the provided request.
*
* @param request - Contains parameters that define the changes to apply to the release.
* @param releaseId - The ID of the release to be updated.
* @param api - An instance of the GitHub API client for interacting with GitHub services.
*
* @returns The updated release data from GitHub.
*/
private async updateRelease(request: GitHubUploadRequest, releaseId: number, api: GitHubApiClient): Promise<GitHubRelease> {
return await api.updateRelease({
...this._context.repo,
id: releaseId,
body: request.changelog,
assets: request.files,
});
}
}

View file

@ -1,115 +0,0 @@
import PublisherTarget from "../publisher-target";
import * as github from "@actions/github";
import File from "../../utils/io/file";
import ModPublisher from "../../publishing/mod-publisher";
import Dependency from "../../metadata/dependency";
import { mapStringInput, mapBooleanInput } from "../../utils/actions/input";
import VersionType from "../../utils/versioning/version-type";
import { env } from "process";
function getEnvironmentTag(): string | undefined {
if (env.GITHUB_REF?.startsWith("refs/tags/")) {
return env.GITHUB_REF.substring(10);
}
return undefined;
}
export default class GitHubPublisher extends ModPublisher {
public get target(): PublisherTarget {
return PublisherTarget.GitHub;
}
protected get requiresId(): boolean {
return false;
}
protected get requiresGameVersions(): boolean {
return false;
}
protected get requiresModLoaders(): boolean {
return false;
}
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 repo = github.context.repo;
const octokit = github.getOctokit(token);
const environmentTag = getEnvironmentTag();
let tag = mapStringInput(options.tag, null);
let releaseId = 0;
if (tag) {
releaseId = await this.getReleaseIdByTag(tag, token);
} else if (github.context.payload.release?.id) {
releaseId = github.context.payload.release?.id;
} else if (environmentTag) {
releaseId = await this.getReleaseIdByTag(environmentTag, token);
} else if (version) {
releaseId = await this.getReleaseIdByTag(version, token);
}
const generated = !releaseId;
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);
const commitish = mapStringInput(options.commitish, null);
const discussion = mapStringInput(options.discussion, null);
releaseId = await this.createRelease(tag, name, changelog, generateChangelog, draft, prerelease, commitish, discussion, token);
}
if (!releaseId) {
throw new Error(`Cannot find or create release ${tag}`);
}
const existingAssets = generated ? [] : (await octokit.rest.repos.listReleaseAssets({ ...repo, release_id: releaseId })).data;
for (const file of files) {
const existingAsset = existingAssets.find(x => x.name === file.name || x.name === file.path);
if (existingAsset) {
await octokit.rest.repos.deleteReleaseAsset({ ...repo, asset_id: existingAsset.id })
}
await octokit.rest.repos.uploadReleaseAsset({
owner: repo.owner,
repo: repo.repo,
release_id: releaseId,
name: file.name,
data: <any>await file.getBuffer()
});
}
}
private async getReleaseIdByTag(tag: string, token: string): Promise<number | undefined> {
const octokit = github.getOctokit(token);
try {
const response = await octokit.rest.repos.getReleaseByTag({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
tag
});
return response.status >= 200 && response.status < 300 ? response.data.id : undefined;
} catch {
return undefined;
}
}
private async createRelease(tag: string, name: string, body: string, generateReleaseNotes: boolean, draft: boolean, prerelease: boolean, targetCommitish: string, discussionCategoryName: string, token: string): Promise<number | undefined> {
const octokit = github.getOctokit(token);
try {
const response = await octokit.rest.repos.createRelease({
tag_name: tag,
owner: github.context.repo.owner,
repo: github.context.repo.repo,
target_commitish: targetCommitish || undefined,
name: name || undefined,
body: body || undefined,
draft,
prerelease,
discussion_category_name: discussionCategoryName || undefined,
generate_release_notes: generateReleaseNotes,
});
return response.status >= 200 && response.status < 300 ? response.data.id : undefined;
} catch {
return undefined;
}
}
}