From 4e7886fe220fe3bdcb6e913da5a51b425a890db9 Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Tue, 7 Jun 2022 21:17:45 +0300 Subject: [PATCH] Made it possible to automatically create GitHub Releases Closes #7 --- .eslintrc | 2 +- README.md | 54 +++++++++++++- action.yml | 32 ++++++-- src/publishing/github/github-publisher.ts | 91 ++++++++++++++++++----- src/publishing/mod-publisher.ts | 18 ++++- 5 files changed, 166 insertions(+), 31 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4a5d3e2..aadfdd5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,7 +11,7 @@ ], "rules": { "arrow-body-style": ["error", "as-needed"], - "complexity": ["warn", { "max": 30 }], + "complexity": ["warn", { "max": 50 }], "curly": ["error", "multi-line", "consistent"], "eqeqeq": ["error", "smart"], "no-constant-condition": ["error", { "checkLoops": false }], diff --git a/README.md b/README.md index 65876ce..f870364 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,11 @@ jobs: curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} github-tag: mc1.17.1-0.3.2 + github-generate-changelog: true + github-draft: false + github-prerelease: false + github-commitish: dev + github-discussion: Announcements github-token: ${{ secrets.GITHUB_TOKEN }} files-primary: build/libs/!(*-@(dev|sources)).jar @@ -96,7 +101,12 @@ jobs: | [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`
`subset`
`intersection`
`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` | +| [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, if any; otherwise it will be inferred from the `GITHUB_REF` environment variable | `mc1.17.1-0.3.2` | +| [github-generate-changelog](#user-content-github-generate-changelog) | Indicates whether to automatically generate the changelog for this release. If changelog is specified, it will be pre-pended to the automatically generated notes. Unused if the GitHub Release already exists | `true`, if [`changelog`](#user-content-changelog) and [`changelog-file`](#user-content-changelog-file) are not provided; otherwise, `false` | `false`
`true` | +| [github-draft](#user-content-github-draft) | `true` to create a draft (unpublished) release, `false` to create a published one. Unused if the GitHub Release already exists | `false` | `false`
`true` | +| [github-prerelease](#user-content-github-prerelease) | `true` to identify the release as a prerelease, `false` to identify the release as a full release. Unused if the GitHub Release already exists | `false`, if [`version-type`](#user-content-version-type) is `release`; otherwise, `true` | `false`
`true` | +| [github-commitish](#user-content-github-commitish) | Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Unused if the Git tag already exists | The repository's default branch | `dev`
`feature/86` | +| [github-discussion](#user-content-github-discussion) | If specified, a discussion of the specified category is created and linked to the release. Unused if the GitHub Release already exists | ❌ | `Announcements` | | [github-token](#user-content-github-token) | A valid token for the GitHub API | ❌ | `${{ secrets.GITHUB_TOKEN }}` | | [files](#user-content-files) | A glob of the file(s) to upload | ❌ | `build/libs/*.jar` | | [files-primary](#user-content-files-primary) | A glob of the primary files to upload | `build/libs/!(*-@(dev\|sources)).jar` | `build/libs/!(*-@(dev\|sources)).jar` | @@ -364,12 +374,52 @@ curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} #### github-tag -The tag name of the release to upload assets to. If no value is provided, a tag of the release that triggered the action will be used. +The tag name of the release to upload assets to. If no value is provided, a tag of the release that triggered the action will be used, if any; otherwise it will be inferred from the `GITHUB_REF` environment variable. ```yaml github-tag: mc1.17.1-0.3.2 ``` +#### github-generate-changelog + +Indicates whether to automatically generate the changelog for this release. If changelog is specified, it will be pre-pended to the automatically generated notes. Unused if the GitHub Release already exists. Default value is `true`, if [`changelog`](#user-content-changelog) and [`changelog-file`](#user-content-changelog-file) are not provided; otherwise, `false`. + +```yaml +github-generate-changelog: false +``` + +#### github-draft + +`true` to create a draft (unpublished) release, `false` to create a published one. Unused if the GitHub Release already exists. Default value is `false`. + +```yaml +github-draft: false +``` + +#### github-prerelease + +`true` to identify the release as a prerelease, `false` to identify the release as a full release. Unused if the GitHub Release already exists. Default value is `false`, if [`version-type`](#user-content-version-type) is `release`; otherwise, `true`. + +```yaml +github-prerelease: true +``` + +#### github-commitish + +Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Unused if the Git tag already exists. Default value is the repository's default branch. + +```yaml +github-commitish: 347040cd637363613e56a6b333f09eaa5be3a196 +``` + +#### github-discussion + +If specified, a discussion of the specified category is created and linked to the release. Unused if the GitHub Release already exists. + +```yaml +github-discussion: Announcements +``` + #### github-token A valid token for the GitHub API. It's required if you want to publish your assets to GitHub. diff --git a/action.yml b/action.yml index f4ced27..f294a3a 100644 --- a/action.yml +++ b/action.yml @@ -9,16 +9,16 @@ inputs: description: The ID of the Modrinth project to upload to required: false default: ${undefined} - modrinth-token: - description: A valid token for the Modrinth API - required: false - default: ${undefined} modrinth-featured: 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: + modrinth-unfeature-mode: + description: Determines the way automatic unfeaturing of older Modrinth versions works + required: false + default: ${undefined} + modrinth-token: + description: A valid token for the Modrinth API required: false default: ${undefined} @@ -35,6 +35,26 @@ inputs: description: The tag name of the release to upload assets to required: false default: ${undefined} + github-generate-changelog: + description: Indicates whether to automatically generate the changelog for this release. If changelog is specified, it will be pre-pended to the automatically generated notes. Unused if the GitHub Release already exists + required: false + default: ${undefined} + github-draft: + description: true to create a draft (unpublished) release, false to create a published one. Unused if the GitHub Release already exists + required: false + default: ${undefined} + github-prerelease: + description: true to identify the release as a prerelease, false to identify the release as a full release. Unused if the GitHub Release already exists + required: false + default: ${undefined} + github-commitish: + description: Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Unused if the Git tag already exists + required: false + default: ${undefined} + github-discussion: + description: If specified, a discussion of the specified category is created and linked to the release. Unused if the GitHub Release already exists + required: false + default: ${undefined} github-token: description: A valid token for the GitHub API required: false diff --git a/src/publishing/github/github-publisher.ts b/src/publishing/github/github-publisher.ts index c732700..fd4fbe6 100644 --- a/src/publishing/github/github-publisher.ts +++ b/src/publishing/github/github-publisher.ts @@ -1,37 +1,55 @@ -import Publisher from "../publisher"; import PublisherTarget from "../publisher-target"; import * as github from "@actions/github"; import { File } from "../../utils/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 { env } from "process"; -interface GitHubPublisherOptions { - tag?: string; - token: string; - files?: string | { primary?: string, secondary?: string }; +function getEnvironmentTag(): string | undefined { + if (env.GITHUB_REF?.startsWith("refs/tags/")) { + return env.GITHUB_REF.substring(10); + } + return undefined; } -export default class GitHubPublisher extends Publisher { +export default class GitHubPublisher extends ModPublisher { public get target(): PublisherTarget { return PublisherTarget.GitHub; } - public async publish(files: File[], options: GitHubPublisherOptions): Promise { - this.validateOptions(options); - let releaseId = 0; + 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(options.token); - if (options.tag) { - const response = await octokit.rest.repos.getReleaseByTag({ ...repo, tag: options.tag }); - if (response.status >= 200 && response.status < 300) { - releaseId = response.data.id; - } - } else { - releaseId = github.context.payload.release?.id; + const octokit = github.getOctokit(token); + let tag = mapStringInput(options.tag, null); + let releaseId = tag ? await this.getReleaseIdByTag(tag, token) : github.context.payload.release?.id; + const generated = !releaseId; + if (!releaseId && (tag ??= getEnvironmentTag() ?? 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(`Couldn't find release #${options.tag || releaseId}`); + throw new Error(`Cannot find or create release #${options.tag || releaseId}`); } - const existingAssets = (await octokit.rest.repos.listReleaseAssets({ ...repo, release_id: releaseId })).data; + 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) { @@ -47,4 +65,39 @@ export default class GitHubPublisher extends Publisher { }); } } + + 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 diff --git a/src/publishing/mod-publisher.ts b/src/publishing/mod-publisher.ts index f7b4830..239dde1 100644 --- a/src/publishing/mod-publisher.ts +++ b/src/publishing/mod-publisher.ts @@ -54,6 +54,18 @@ async function readChangelog(changelogPath: string): Promise { } export default abstract class ModPublisher extends Publisher { + protected get requiresId(): boolean { + return true; + } + + protected get requiresModLoaders(): boolean { + return true; + } + + protected get requiresGameVersions(): boolean { + return true; + } + public async publish(files: File[], options: ModPublisherOptions): Promise { this.validateOptions(options); const releaseInfo = context.payload.release; @@ -70,7 +82,7 @@ export default abstract class ModPublisher extends PublisherreleaseInfo?.body || ""; const loaders = processMultilineInput(options.loaders, /\s+/); - if (!loaders.length) { + if (!loaders.length && this.requiresModLoaders) { if (metadata) { loaders.push(...metadata.loaders); } @@ -95,7 +107,7 @@ export default abstract class ModPublisher extends Publisher x.id === "minecraft").map(x => parseVersionName(x.version))[0] || parseVersionNameFromFileVersion(version);