Thomas Piskol 9729932bfb
Support custom target_commitish value (#76)
* Make target_commitish configurable

* Store compiled action for testing

* Add target_commitish to metadata

* Allow override of target_commitish for updating

* Print commit usage

* Improve message for commit usage

* Update draft releases too

* Revert "Update draft releases too"

This reverts commit ff30f8edb18fa851785a2d361b03c9713f5ac17d.

* Integrate latest changes from upstream.
2021-05-02 20:43:58 -04:00

242 lines
6.0 KiB
TypeScript

import { GitHub } from "@actions/github";
import { Config, releaseBody } from "./util";
import { lstatSync, readFileSync } from "fs";
import { getType } from "mime";
import { basename } from "path";
export interface ReleaseAsset {
name: string;
mime: string;
size: number;
file: Buffer;
}
export interface Release {
id: number;
upload_url: string;
html_url: string;
tag_name: string;
body: string;
target_commitish: string;
}
export interface Releaser {
getReleaseByTag(params: {
owner: string;
repo: string;
tag: string;
}): Promise<{ data: Release }>;
createRelease(params: {
owner: string;
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
target_commitish: string | undefined;
}): Promise<{ data: Release }>;
updateRelease(params: {
owner: string;
repo: string;
release_id: number;
tag_name: string;
target_commitish: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
}): Promise<{ data: Release }>;
allReleases(params: {
owner: string;
repo: string;
}): AsyncIterableIterator<{ data: Release[] }>;
}
export class GitHubReleaser implements Releaser {
github: GitHub;
constructor(github: GitHub) {
this.github = github;
}
getReleaseByTag(params: {
owner: string;
repo: string;
tag: string;
}): Promise<{ data: Release }> {
return this.github.repos.getReleaseByTag(params);
}
createRelease(params: {
owner: string;
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
target_commitish: string | undefined;
}): Promise<{ data: Release }> {
return this.github.repos.createRelease(params);
}
updateRelease(params: {
owner: string;
repo: string;
release_id: number;
tag_name: string;
target_commitish: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
}): Promise<{ data: Release }> {
return this.github.repos.updateRelease(params);
}
allReleases(params: {
owner: string;
repo: string;
}): AsyncIterableIterator<{ data: Release[] }> {
const updatedParams = { per_page: 100, ...params };
return this.github.paginate.iterator(
this.github.repos.listReleases.endpoint.merge(updatedParams)
);
}
}
export const asset = (path: string): ReleaseAsset => {
return {
name: basename(path),
mime: mimeOrDefault(path),
size: lstatSync(path).size,
file: readFileSync(path)
};
};
export const mimeOrDefault = (path: string): string => {
return getType(path) || "application/octet-stream";
};
export const upload = async (
gh: GitHub,
url: string,
path: string
): Promise<any> => {
let { name, size, mime, file } = asset(path);
console.log(`⬆️ Uploading ${name}...`);
return await gh.repos.uploadReleaseAsset({
url,
headers: {
"content-length": size,
"content-type": mime
},
name,
file
});
};
export const release = async (
config: Config,
releaser: Releaser
): Promise<Release> => {
const [owner, repo] = config.github_repository.split("/");
const tag =
config.input_tag_name || config.github_ref.replace("refs/tags/", "");
try {
// you can't get a an existing draft by tag
// so we must find one in the list of all releases
if (config.input_draft) {
for await (const response of releaser.allReleases({
owner,
repo
})) {
let release = response.data.find(release => release.tag_name === tag);
if (release) {
return release;
}
}
}
let existingRelease = await releaser.getReleaseByTag({
owner,
repo,
tag
});
const release_id = existingRelease.data.id;
let target_commitish: string;
if (
config.input_target_commitish &&
config.input_target_commitish !== existingRelease.data.target_commitish
) {
console.log(
`Updating commit from "${existingRelease.data.target_commitish}" to "${config.input_target_commitish}"`
);
target_commitish = config.input_target_commitish;
} else {
target_commitish = existingRelease.data.target_commitish;
}
const tag_name = tag;
const name = config.input_name || tag;
const body = `${existingRelease.data.body}\n${releaseBody(config)}`;
const draft = config.input_draft;
const prerelease = config.input_prerelease;
const release = await releaser.updateRelease({
owner,
repo,
release_id,
tag_name,
target_commitish,
name,
body,
draft,
prerelease
});
return release.data;
} catch (error) {
if (error.status === 404) {
const tag_name = tag;
const name = config.input_name || tag;
const body = releaseBody(config);
const draft = config.input_draft;
const prerelease = config.input_prerelease;
const target_commitish = config.input_target_commitish;
let commitMessage: string = "";
if (target_commitish) {
commitMessage = ` using commit "${target_commitish}"`;
}
console.log(
`👩‍🏭 Creating new GitHub release for tag ${tag_name}${commitMessage}...`
);
try {
let release = await releaser.createRelease({
owner,
repo,
tag_name,
name,
body,
draft,
prerelease,
target_commitish
});
return release.data;
} catch (error) {
// presume a race with competing metrix runs
console.log(
`⚠️ GitHub release failed with status: ${error.status}, retrying...`
);
return release(config, releaser);
}
} else {
console.log(
`⚠️ Unexpected error fetching GitHub release for tag ${config.github_ref}: ${error}`
);
throw error;
}
}
};