Made it possible to automatically create GitHub Releases

Closes #7
This commit is contained in:
Kir_Antipov 2022-06-07 21:17:45 +03:00
parent b4d00806ed
commit 4e7886fe22
5 changed files with 166 additions and 31 deletions

View file

@ -11,7 +11,7 @@
], ],
"rules": { "rules": {
"arrow-body-style": ["error", "as-needed"], "arrow-body-style": ["error", "as-needed"],
"complexity": ["warn", { "max": 30 }], "complexity": ["warn", { "max": 50 }],
"curly": ["error", "multi-line", "consistent"], "curly": ["error", "multi-line", "consistent"],
"eqeqeq": ["error", "smart"], "eqeqeq": ["error", "smart"],
"no-constant-condition": ["error", { "checkLoops": false }], "no-constant-condition": ["error", { "checkLoops": false }],

View file

@ -24,6 +24,11 @@ jobs:
curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
github-tag: mc1.17.1-0.3.2 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 }} github-token: ${{ secrets.GITHUB_TOKEN }}
files-primary: build/libs/!(*-@(dev|sources)).jar 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` <br> `subset` <br> `intersection` <br> `any` | | [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` <br> `subset` <br> `intersection` <br> `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-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 }}` | | [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` <br> `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` <br> `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` <br> `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` <br> `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 }}` | | [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](#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` | | [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 #### 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 ```yaml
github-tag: mc1.17.1-0.3.2 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 #### github-token
A valid token for the GitHub API. It's required if you want to publish your assets to GitHub. A valid token for the GitHub API. It's required if you want to publish your assets to GitHub.

View file

@ -9,16 +9,16 @@ inputs:
description: The ID of the Modrinth project to upload to description: The ID of the Modrinth project to upload to
required: false required: false
default: ${undefined} default: ${undefined}
modrinth-token:
description: A valid token for the Modrinth API
required: false
default: ${undefined}
modrinth-featured: modrinth-featured:
description: Indicates whether the version should be featured on Modrinth or not description: Indicates whether the version should be featured on Modrinth or not
required: false required: false
default: ${undefined} default: ${undefined}
modrinth-unfeature-mode: Determines the way automatic unfeaturing of older Modrinth versions works modrinth-unfeature-mode:
description: 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 required: false
default: ${undefined} default: ${undefined}
@ -35,6 +35,26 @@ inputs:
description: The tag name of the release to upload assets to description: The tag name of the release to upload assets to
required: false required: false
default: ${undefined} 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: github-token:
description: A valid token for the GitHub API description: A valid token for the GitHub API
required: false required: false

View file

@ -1,37 +1,55 @@
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 { File } from "../../utils/file"; 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 { function getEnvironmentTag(): string | undefined {
tag?: string; if (env.GITHUB_REF?.startsWith("refs/tags/")) {
token: string; return env.GITHUB_REF.substring(10);
files?: string | { primary?: string, secondary?: string }; }
return undefined;
} }
export default class GitHubPublisher extends Publisher<GitHubPublisherOptions> { export default class GitHubPublisher extends ModPublisher {
public get target(): PublisherTarget { public get target(): PublisherTarget {
return PublisherTarget.GitHub; return PublisherTarget.GitHub;
} }
public async publish(files: File[], options: GitHubPublisherOptions): Promise<void> { protected get requiresId(): boolean {
this.validateOptions(options); return false;
let releaseId = 0;
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;
}
if (!releaseId) {
throw new Error(`Couldn't find release #${options.tag || releaseId}`);
} }
const existingAssets = (await octokit.rest.repos.listReleaseAssets({ ...repo, release_id: releaseId })).data; 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);
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(`Cannot find or create release #${options.tag || releaseId}`);
}
const existingAssets = generated ? [] : (await octokit.rest.repos.listReleaseAssets({ ...repo, release_id: releaseId })).data;
for (const file of files) { for (const file of files) {
const existingAsset = existingAssets.find(x => x.name === file.name || x.name === file.path); const existingAsset = existingAssets.find(x => x.name === file.name || x.name === file.path);
if (existingAsset) { if (existingAsset) {
@ -47,4 +65,39 @@ export default class GitHubPublisher extends Publisher<GitHubPublisherOptions> {
}); });
} }
} }
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;
}
}
} }

View file

@ -54,6 +54,18 @@ async function readChangelog(changelogPath: string): Promise<string | never> {
} }
export default abstract class ModPublisher extends Publisher<ModPublisherOptions> { export default abstract class ModPublisher extends Publisher<ModPublisherOptions> {
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<void> { public async publish(files: File[], options: ModPublisherOptions): Promise<void> {
this.validateOptions(options); this.validateOptions(options);
const releaseInfo = <any>context.payload.release; const releaseInfo = <any>context.payload.release;
@ -70,7 +82,7 @@ export default abstract class ModPublisher extends Publisher<ModPublisherOptions
const metadata = await ModMetadataReader.readMetadata(files[0].path); const metadata = await ModMetadataReader.readMetadata(files[0].path);
const id = options.id || metadata?.getProjectId(this.target); const id = options.id || metadata?.getProjectId(this.target);
if (!id) { if (!id && this.requiresId) {
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)}`);
} }
@ -85,7 +97,7 @@ export default abstract class ModPublisher extends Publisher<ModPublisherOptions
: <string>releaseInfo?.body || ""; : <string>releaseInfo?.body || "";
const loaders = processMultilineInput(options.loaders, /\s+/); const loaders = processMultilineInput(options.loaders, /\s+/);
if (!loaders.length) { if (!loaders.length && this.requiresModLoaders) {
if (metadata) { if (metadata) {
loaders.push(...metadata.loaders); loaders.push(...metadata.loaders);
} }
@ -95,7 +107,7 @@ export default abstract class ModPublisher extends Publisher<ModPublisherOptions
} }
const gameVersions = processMultilineInput(options.gameVersions); const gameVersions = processMultilineInput(options.gameVersions);
if (!gameVersions.length) { if (!gameVersions.length && this.requiresGameVersions) {
const minecraftVersion = const minecraftVersion =
metadata?.dependencies.filter(x => x.id === "minecraft").map(x => parseVersionName(x.version))[0] || metadata?.dependencies.filter(x => x.id === "minecraft").map(x => parseVersionName(x.version))[0] ||
parseVersionNameFromFileVersion(version); parseVersionNameFromFileVersion(version);