mirror of
https://github.com/softprops/action-gh-release.git
synced 2025-05-11 19:04:20 +00:00
support for asset retries, and deleting existing releases
This commit is contained in:
parent
0465cdad11
commit
0edcedf52f
8 changed files with 202 additions and 56 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -23,4 +23,4 @@ jobs:
|
||||||
# git diff --exit-code --stat -- . ':!node_modules' \
|
# git diff --exit-code --stat -- . ':!node_modules' \
|
||||||
# || (echo "##[error] found changed files after build. please 'npm run build && npm run fmt'" \
|
# || (echo "##[error] found changed files after build. please 'npm run build && npm run fmt'" \
|
||||||
# "and check in all changes" \
|
# "and check in all changes" \
|
||||||
# && exit 1)
|
# && exit 1)
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@ __tests__/runner/*
|
||||||
# actions requires a node_modules dir https://github.com/actions/toolkit/blob/master/docs/javascript-action.md#publish-a-releasesv1-action
|
# actions requires a node_modules dir https://github.com/actions/toolkit/blob/master/docs/javascript-action.md#publish-a-releasesv1-action
|
||||||
# but its recommended not to check these in https://github.com/actions/toolkit/blob/master/docs/action-versioning.md#recommendations
|
# but its recommended not to check these in https://github.com/actions/toolkit/blob/master/docs/action-versioning.md#recommendations
|
||||||
node_modules
|
node_modules
|
||||||
|
.idea
|
||||||
|
|
|
@ -180,6 +180,9 @@ The following are optional as `step.with` keys
|
||||||
| `token` | String | Secret GitHub Personal Access Token. Defaults to `${{ github.token }}` |
|
| `token` | String | Secret GitHub Personal Access Token. Defaults to `${{ github.token }}` |
|
||||||
| `discussion_category_name` | String | If specified, a discussion of the specified category is created and linked to the release. The value must be a category that already exists in the repository. For more information, see ["Managing categories for discussions in your repository."](https://docs.github.com/en/discussions/managing-discussions-for-your-community/managing-categories-for-discussions-in-your-repository) |
|
| `discussion_category_name` | String | If specified, a discussion of the specified category is created and linked to the release. The value must be a category that already exists in the repository. For more information, see ["Managing categories for discussions in your repository."](https://docs.github.com/en/discussions/managing-discussions-for-your-community/managing-categories-for-discussions-in-your-repository) |
|
||||||
| `generate_release_notes` | Boolean | Whether to automatically generate the name and body for this release. If name is specified, the specified name will be used; otherwise, a name will be automatically generated. If body is specified, the body will be pre-pended to the automatically generated notes. |
|
| `generate_release_notes` | Boolean | Whether to automatically generate the name and body for this release. If name is specified, the specified name will be used; otherwise, a name will be automatically generated. If body is specified, the body will be pre-pended to the automatically generated notes. |
|
||||||
|
| `retries` | Number | The amount of times you want to try to upload a release asset. This will default to 0 which means do not retry. |
|
||||||
|
| `retry_interval` | Number | The interval in millisecond between failed asset upload attempts. |
|
||||||
|
| `delete_on_existing` | Boolean | Whether or not to delete an existing release entirely when running this action. |
|
||||||
|
|
||||||
💡 When providing a `body` and `body_path` at the same time, `body_path` will be
|
💡 When providing a `body` and `body_path` at the same time, `body_path` will be
|
||||||
attempted first, then falling back on `body` if the path can not be read from.
|
attempted first, then falling back on `body` if the path can not be read from.
|
||||||
|
|
|
@ -51,7 +51,10 @@ describe("util", () => {
|
||||||
input_tag_name: undefined,
|
input_tag_name: undefined,
|
||||||
input_target_commitish: undefined,
|
input_target_commitish: undefined,
|
||||||
input_discussion_category_name: undefined,
|
input_discussion_category_name: undefined,
|
||||||
input_generate_release_notes: false
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -71,7 +74,10 @@ describe("util", () => {
|
||||||
input_tag_name: undefined,
|
input_tag_name: undefined,
|
||||||
input_target_commitish: undefined,
|
input_target_commitish: undefined,
|
||||||
input_discussion_category_name: undefined,
|
input_discussion_category_name: undefined,
|
||||||
input_generate_release_notes: false
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -91,7 +97,10 @@ describe("util", () => {
|
||||||
input_tag_name: undefined,
|
input_tag_name: undefined,
|
||||||
input_target_commitish: undefined,
|
input_target_commitish: undefined,
|
||||||
input_discussion_category_name: undefined,
|
input_discussion_category_name: undefined,
|
||||||
input_generate_release_notes: false
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -123,7 +132,10 @@ describe("util", () => {
|
||||||
input_fail_on_unmatched_files: false,
|
input_fail_on_unmatched_files: false,
|
||||||
input_target_commitish: undefined,
|
input_target_commitish: undefined,
|
||||||
input_discussion_category_name: undefined,
|
input_discussion_category_name: undefined,
|
||||||
input_generate_release_notes: false
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -147,7 +159,10 @@ describe("util", () => {
|
||||||
input_fail_on_unmatched_files: false,
|
input_fail_on_unmatched_files: false,
|
||||||
input_target_commitish: "affa18ef97bc9db20076945705aba8c516139abd",
|
input_target_commitish: "affa18ef97bc9db20076945705aba8c516139abd",
|
||||||
input_discussion_category_name: undefined,
|
input_discussion_category_name: undefined,
|
||||||
input_generate_release_notes: false
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -170,7 +185,10 @@ describe("util", () => {
|
||||||
input_fail_on_unmatched_files: false,
|
input_fail_on_unmatched_files: false,
|
||||||
input_target_commitish: undefined,
|
input_target_commitish: undefined,
|
||||||
input_discussion_category_name: "releases",
|
input_discussion_category_name: "releases",
|
||||||
input_generate_release_notes: false
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -194,7 +212,65 @@ describe("util", () => {
|
||||||
input_fail_on_unmatched_files: false,
|
input_fail_on_unmatched_files: false,
|
||||||
input_target_commitish: undefined,
|
input_target_commitish: undefined,
|
||||||
input_discussion_category_name: undefined,
|
input_discussion_category_name: undefined,
|
||||||
input_generate_release_notes: true
|
input_generate_release_notes: true,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports providing retry options", () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
parseConfig({
|
||||||
|
INPUT_RETRIES: "3",
|
||||||
|
INPUT_RETRY_INTERVAL: "1000"
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
github_ref: "",
|
||||||
|
github_repository: "",
|
||||||
|
github_token: "",
|
||||||
|
input_body: undefined,
|
||||||
|
input_body_path: undefined,
|
||||||
|
input_draft: undefined,
|
||||||
|
input_prerelease: undefined,
|
||||||
|
input_files: [],
|
||||||
|
input_name: undefined,
|
||||||
|
input_tag_name: undefined,
|
||||||
|
input_fail_on_unmatched_files: false,
|
||||||
|
input_target_commitish: undefined,
|
||||||
|
input_discussion_category_name: undefined,
|
||||||
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 3,
|
||||||
|
input_retry_interval: 1000,
|
||||||
|
input_delete_on_existing: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports deleting existing releases", () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
parseConfig({
|
||||||
|
INPUT_DELETE_ON_EXISTING: "true"
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
github_ref: "",
|
||||||
|
github_repository: "",
|
||||||
|
github_token: "",
|
||||||
|
input_body: undefined,
|
||||||
|
input_body_path: undefined,
|
||||||
|
input_draft: undefined,
|
||||||
|
input_prerelease: undefined,
|
||||||
|
input_files: [],
|
||||||
|
input_name: undefined,
|
||||||
|
input_tag_name: undefined,
|
||||||
|
input_fail_on_unmatched_files: false,
|
||||||
|
input_target_commitish: undefined,
|
||||||
|
input_discussion_category_name: undefined,
|
||||||
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -221,7 +297,10 @@ describe("util", () => {
|
||||||
input_fail_on_unmatched_files: false,
|
input_fail_on_unmatched_files: false,
|
||||||
input_target_commitish: undefined,
|
input_target_commitish: undefined,
|
||||||
input_discussion_category_name: undefined,
|
input_discussion_category_name: undefined,
|
||||||
input_generate_release_notes: false
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -246,7 +325,10 @@ describe("util", () => {
|
||||||
input_fail_on_unmatched_files: false,
|
input_fail_on_unmatched_files: false,
|
||||||
input_target_commitish: undefined,
|
input_target_commitish: undefined,
|
||||||
input_discussion_category_name: undefined,
|
input_discussion_category_name: undefined,
|
||||||
input_generate_release_notes: false
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -270,7 +352,10 @@ describe("util", () => {
|
||||||
input_fail_on_unmatched_files: false,
|
input_fail_on_unmatched_files: false,
|
||||||
input_target_commitish: undefined,
|
input_target_commitish: undefined,
|
||||||
input_discussion_category_name: undefined,
|
input_discussion_category_name: undefined,
|
||||||
input_generate_release_notes: false
|
input_generate_release_notes: false,
|
||||||
|
input_retries: 0,
|
||||||
|
input_retry_interval: 0,
|
||||||
|
input_delete_on_existing: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,6 +43,15 @@ inputs:
|
||||||
generate_release_notes:
|
generate_release_notes:
|
||||||
description: "Whether to automatically generate the name and body for this release. If name is specified, the specified name will be used; otherwise, a name will be automatically generated. If body is specified, the body will be pre-pended to the automatically generated notes."
|
description: "Whether to automatically generate the name and body for this release. If name is specified, the specified name will be used; otherwise, a name will be automatically generated. If body is specified, the body will be pre-pended to the automatically generated notes."
|
||||||
required: false
|
required: false
|
||||||
|
retries:
|
||||||
|
description: "The amount of times you want to try to upload a release asset. This will default to 0 which means do not retry."
|
||||||
|
required: false
|
||||||
|
retry_interval:
|
||||||
|
description: "The interval in millisecond between failed asset upload attempts."
|
||||||
|
required: false
|
||||||
|
delete_on_existing:
|
||||||
|
description: "Whether or not to delete an existing release entirely when running this action."
|
||||||
|
required: false
|
||||||
env:
|
env:
|
||||||
"GITHUB_TOKEN": "As provided by Github Actions"
|
"GITHUB_TOKEN": "As provided by Github Actions"
|
||||||
outputs:
|
outputs:
|
||||||
|
|
2
dist/index.js
vendored
2
dist/index.js
vendored
File diff suppressed because one or more lines are too long
126
src/github.ts
126
src/github.ts
|
@ -61,6 +61,12 @@ export interface Releaser {
|
||||||
generate_release_notes: boolean | undefined;
|
generate_release_notes: boolean | undefined;
|
||||||
}): Promise<{ data: Release }>;
|
}): Promise<{ data: Release }>;
|
||||||
|
|
||||||
|
deleteRelease(params: {
|
||||||
|
owner: string;
|
||||||
|
repo: string;
|
||||||
|
release_id: number;
|
||||||
|
}): Promise<{ data: Release }>;
|
||||||
|
|
||||||
allReleases(params: {
|
allReleases(params: {
|
||||||
owner: string;
|
owner: string;
|
||||||
repo: string;
|
repo: string;
|
||||||
|
@ -112,6 +118,14 @@ export class GitHubReleaser implements Releaser {
|
||||||
return this.github.rest.repos.updateRelease(params);
|
return this.github.rest.repos.updateRelease(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteRelease(params: {
|
||||||
|
owner: string;
|
||||||
|
repo: string;
|
||||||
|
release_id: number;
|
||||||
|
}): Promise<{ data: Release }> {
|
||||||
|
return this.github.rest.repos.deleteRelease(params);
|
||||||
|
}
|
||||||
|
|
||||||
allReleases(params: {
|
allReleases(params: {
|
||||||
owner: string;
|
owner: string;
|
||||||
repo: string;
|
repo: string;
|
||||||
|
@ -141,7 +155,8 @@ export const upload = async (
|
||||||
github: GitHub,
|
github: GitHub,
|
||||||
url: string,
|
url: string,
|
||||||
path: string,
|
path: string,
|
||||||
currentAssets: Array<{ id: number; name: string }>
|
currentAssets: Array<{ id: number; name: string }>,
|
||||||
|
maxRetries: number = config.input_retries || 0
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
const [owner, repo] = config.github_repository.split("/");
|
const [owner, repo] = config.github_repository.split("/");
|
||||||
const { name, size, mime, data: body } = asset(path);
|
const { name, size, mime, data: body } = asset(path);
|
||||||
|
@ -170,11 +185,23 @@ export const upload = async (
|
||||||
});
|
});
|
||||||
const json = await resp.json();
|
const json = await resp.json();
|
||||||
if (resp.status !== 201) {
|
if (resp.status !== 201) {
|
||||||
throw new Error(
|
if (maxRetries <= 0) {
|
||||||
`Failed to upload release asset ${name}. received status code ${
|
throw new Error(
|
||||||
resp.status
|
`Failed to upload release asset ${name}. received status code ${
|
||||||
}\n${json.message}\n${JSON.stringify(json.errors)}`
|
resp.status
|
||||||
|
}\n${json.message}\n${JSON.stringify(json.errors)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Failed to upload asset ${name} (${maxRetries - 1} retries remaining).`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (config.input_retry_interval) {
|
||||||
|
await new Promise(r => setTimeout(r, config.input_retry_interval));
|
||||||
|
}
|
||||||
|
|
||||||
|
return upload(config, github, url, path, currentAssets, maxRetries - 1);
|
||||||
}
|
}
|
||||||
return json;
|
return json;
|
||||||
};
|
};
|
||||||
|
@ -198,6 +225,49 @@ export const release = async (
|
||||||
|
|
||||||
const discussion_category_name = config.input_discussion_category_name;
|
const discussion_category_name = config.input_discussion_category_name;
|
||||||
const generate_release_notes = config.input_generate_release_notes;
|
const generate_release_notes = config.input_generate_release_notes;
|
||||||
|
|
||||||
|
const createRelease = async () => {
|
||||||
|
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,
|
||||||
|
discussion_category_name,
|
||||||
|
generate_release_notes
|
||||||
|
});
|
||||||
|
return release.data;
|
||||||
|
} catch (error) {
|
||||||
|
// presume a race with competing metrix runs
|
||||||
|
console.log(
|
||||||
|
`⚠️ GitHub release failed with status: ${
|
||||||
|
error.status
|
||||||
|
}\n${JSON.stringify(
|
||||||
|
error.response.data.errors
|
||||||
|
)}\nretrying... (${maxRetries - 1} retries remaining)`
|
||||||
|
);
|
||||||
|
|
||||||
|
return release(config, releaser, maxRetries - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// you can't get a an existing draft by tag
|
// you can't get a an existing draft by tag
|
||||||
// so we must find one in the list of all releases
|
// so we must find one in the list of all releases
|
||||||
|
@ -219,6 +289,13 @@ export const release = async (
|
||||||
});
|
});
|
||||||
|
|
||||||
const release_id = existingRelease.data.id;
|
const release_id = existingRelease.data.id;
|
||||||
|
|
||||||
|
if (config.input_delete_on_existing) {
|
||||||
|
console.log(`⚠ Deleting existing release ${owner}/${repo}@${release_id}`);
|
||||||
|
await releaser.deleteRelease({ owner, repo, release_id });
|
||||||
|
return createRelease();
|
||||||
|
}
|
||||||
|
|
||||||
let target_commitish: string;
|
let target_commitish: string;
|
||||||
if (
|
if (
|
||||||
config.input_target_commitish &&
|
config.input_target_commitish &&
|
||||||
|
@ -265,44 +342,7 @@ export const release = async (
|
||||||
return release.data;
|
return release.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.status === 404) {
|
if (error.status === 404) {
|
||||||
const tag_name = tag;
|
return createRelease();
|
||||||
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,
|
|
||||||
discussion_category_name,
|
|
||||||
generate_release_notes
|
|
||||||
});
|
|
||||||
return release.data;
|
|
||||||
} catch (error) {
|
|
||||||
// presume a race with competing metrix runs
|
|
||||||
console.log(
|
|
||||||
`⚠️ GitHub release failed with status: ${
|
|
||||||
error.status
|
|
||||||
}\n${JSON.stringify(
|
|
||||||
error.response.data.errors
|
|
||||||
)}\nretrying... (${maxRetries - 1} retries remaining)`
|
|
||||||
);
|
|
||||||
return release(config, releaser, maxRetries - 1);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
`⚠️ Unexpected error fetching GitHub release for tag ${config.github_ref}: ${error}`
|
`⚠️ Unexpected error fetching GitHub release for tag ${config.github_ref}: ${error}`
|
||||||
|
|
10
src/util.ts
10
src/util.ts
|
@ -18,6 +18,9 @@ export interface Config {
|
||||||
input_target_commitish?: string;
|
input_target_commitish?: string;
|
||||||
input_discussion_category_name?: string;
|
input_discussion_category_name?: string;
|
||||||
input_generate_release_notes?: boolean;
|
input_generate_release_notes?: boolean;
|
||||||
|
input_retries?: number;
|
||||||
|
input_retry_interval?: number;
|
||||||
|
input_delete_on_existing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const uploadUrl = (url: string): string => {
|
export const uploadUrl = (url: string): string => {
|
||||||
|
@ -67,7 +70,12 @@ export const parseConfig = (env: Env): Config => {
|
||||||
input_target_commitish: env.INPUT_TARGET_COMMITISH || undefined,
|
input_target_commitish: env.INPUT_TARGET_COMMITISH || undefined,
|
||||||
input_discussion_category_name:
|
input_discussion_category_name:
|
||||||
env.INPUT_DISCUSSION_CATEGORY_NAME || undefined,
|
env.INPUT_DISCUSSION_CATEGORY_NAME || undefined,
|
||||||
input_generate_release_notes: env.INPUT_GENERATE_RELEASE_NOTES == "true"
|
input_generate_release_notes: env.INPUT_GENERATE_RELEASE_NOTES == "true",
|
||||||
|
input_retries: env.INPUT_RETRIES ? parseInt(env.INPUT_RETRIES) : 0,
|
||||||
|
input_retry_interval: env.INPUT_RETRY_INTERVAL
|
||||||
|
? parseInt(env.INPUT_RETRY_INTERVAL)
|
||||||
|
: 0,
|
||||||
|
input_delete_on_existing: !!env.INPUT_DELETE_ON_EXISTING
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue