From 49cd72c033e14297eb81b0b06059a1005e3a9074 Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Sat, 22 Apr 2023 13:46:18 +0000 Subject: [PATCH] Refactored `GitHubPublisher` (-> `GitHubUploader`) --- src/platforms/github/github-uploader.ts | 136 ++++++++++++++++++++++ src/publishing/github/github-publisher.ts | 115 ------------------ 2 files changed, 136 insertions(+), 115 deletions(-) create mode 100644 src/platforms/github/github-uploader.ts delete mode 100644 src/publishing/github/github-publisher.ts diff --git a/src/platforms/github/github-uploader.ts b/src/platforms/github/github-uploader.ts new file mode 100644 index 0000000..ef90e73 --- /dev/null +++ b/src/platforms/github/github-uploader.ts @@ -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 { + /** + * 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 { + 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 { + 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 { + return await api.updateRelease({ + ...this._context.repo, + id: releaseId, + body: request.changelog, + assets: request.files, + }); + } +} diff --git a/src/publishing/github/github-publisher.ts b/src/publishing/github/github-publisher.ts deleted file mode 100644 index cf3802a..0000000 --- a/src/publishing/github/github-publisher.ts +++ /dev/null @@ -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): Promise { - 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: await file.getBuffer() - }); - } - } - - private async getReleaseIdByTag(tag: string, token: string): Promise { - 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 { - 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; - } - } -} \ No newline at end of file