mirror of
https://github.com/softprops/action-gh-release.git
synced 2025-11-23 11:50:51 +00:00
Merge 8b7c9633c7 into 5be0e66d93
This commit is contained in:
commit
a70c4f1f45
2 changed files with 69 additions and 452 deletions
|
|
@ -1,6 +1,5 @@
|
||||||
import {
|
import {
|
||||||
asset,
|
asset,
|
||||||
findTagFromReleases,
|
|
||||||
mimeOrDefault,
|
mimeOrDefault,
|
||||||
release,
|
release,
|
||||||
Release,
|
Release,
|
||||||
|
|
@ -28,223 +27,10 @@ describe('github', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('findTagFromReleases', () => {
|
describe('release', () => {
|
||||||
const owner = 'owner';
|
it('creates a new release', async () => {
|
||||||
const repo = 'repo';
|
|
||||||
|
|
||||||
const mockRelease: Release = {
|
|
||||||
id: 1,
|
|
||||||
upload_url: `https://api.github.com/repos/${owner}/${repo}/releases/1/assets`,
|
|
||||||
html_url: `https://github.com/${owner}/${repo}/releases/tag/v1.0.0`,
|
|
||||||
tag_name: 'v1.0.0',
|
|
||||||
name: 'Test Release',
|
|
||||||
body: 'Test body',
|
|
||||||
target_commitish: 'main',
|
|
||||||
draft: false,
|
|
||||||
prerelease: false,
|
|
||||||
assets: [],
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const mockReleaser: Releaser = {
|
|
||||||
getReleaseByTag: () => Promise.reject('Not implemented'),
|
|
||||||
createRelease: () => Promise.reject('Not implemented'),
|
|
||||||
updateRelease: () => Promise.reject('Not implemented'),
|
|
||||||
allReleases: async function* () {
|
|
||||||
yield { data: [mockRelease] };
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
describe('when the tag_name is not an empty string', () => {
|
|
||||||
const targetTag = 'v1.0.0';
|
|
||||||
|
|
||||||
it('finds a matching release in first batch of results', async () => {
|
|
||||||
const targetRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: targetTag,
|
|
||||||
};
|
|
||||||
const otherRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: 'v1.0.1',
|
|
||||||
};
|
|
||||||
|
|
||||||
const releaser = {
|
|
||||||
...mockReleaser,
|
|
||||||
allReleases: async function* () {
|
|
||||||
yield { data: [targetRelease] };
|
|
||||||
yield { data: [otherRelease] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await findTagFromReleases(releaser, owner, repo, targetTag);
|
|
||||||
|
|
||||||
assert.deepStrictEqual(result, targetRelease);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('finds a matching release in second batch of results', async () => {
|
|
||||||
const targetRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: targetTag,
|
|
||||||
};
|
|
||||||
const otherRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: 'v1.0.1',
|
|
||||||
};
|
|
||||||
|
|
||||||
const releaser = {
|
|
||||||
...mockReleaser,
|
|
||||||
allReleases: async function* () {
|
|
||||||
yield { data: [otherRelease] };
|
|
||||||
yield { data: [targetRelease] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await findTagFromReleases(releaser, owner, repo, targetTag);
|
|
||||||
assert.deepStrictEqual(result, targetRelease);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns undefined when a release is not found in any batch', async () => {
|
|
||||||
const otherRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: 'v1.0.1',
|
|
||||||
};
|
|
||||||
const releaser = {
|
|
||||||
...mockReleaser,
|
|
||||||
allReleases: async function* () {
|
|
||||||
yield { data: [otherRelease] };
|
|
||||||
yield { data: [otherRelease] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await findTagFromReleases(releaser, owner, repo, targetTag);
|
|
||||||
|
|
||||||
assert.strictEqual(result, undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns undefined when no releases are returned', async () => {
|
|
||||||
const releaser = {
|
|
||||||
...mockReleaser,
|
|
||||||
allReleases: async function* () {
|
|
||||||
yield { data: [] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await findTagFromReleases(releaser, owner, repo, targetTag);
|
|
||||||
|
|
||||||
assert.strictEqual(result, undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the tag_name is an empty string', () => {
|
|
||||||
const emptyTag = '';
|
|
||||||
|
|
||||||
it('finds a matching release in first batch of results', async () => {
|
|
||||||
const targetRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: emptyTag,
|
|
||||||
};
|
|
||||||
const otherRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: 'v1.0.1',
|
|
||||||
};
|
|
||||||
|
|
||||||
const releaser = {
|
|
||||||
...mockReleaser,
|
|
||||||
allReleases: async function* () {
|
|
||||||
yield { data: [targetRelease] };
|
|
||||||
yield { data: [otherRelease] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await findTagFromReleases(releaser, owner, repo, emptyTag);
|
|
||||||
|
|
||||||
assert.deepStrictEqual(result, targetRelease);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('finds a matching release in second batch of results', async () => {
|
|
||||||
const targetRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: emptyTag,
|
|
||||||
};
|
|
||||||
const otherRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: 'v1.0.1',
|
|
||||||
};
|
|
||||||
|
|
||||||
const releaser = {
|
|
||||||
...mockReleaser,
|
|
||||||
allReleases: async function* () {
|
|
||||||
yield { data: [otherRelease] };
|
|
||||||
yield { data: [targetRelease] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await findTagFromReleases(releaser, owner, repo, emptyTag);
|
|
||||||
assert.deepStrictEqual(result, targetRelease);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns undefined when a release is not found in any batch', async () => {
|
|
||||||
const otherRelease = {
|
|
||||||
...mockRelease,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
tag_name: 'v1.0.1',
|
|
||||||
};
|
|
||||||
const releaser = {
|
|
||||||
...mockReleaser,
|
|
||||||
allReleases: async function* () {
|
|
||||||
yield { data: [otherRelease] };
|
|
||||||
yield { data: [otherRelease] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await findTagFromReleases(releaser, owner, repo, emptyTag);
|
|
||||||
|
|
||||||
assert.strictEqual(result, undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns undefined when no releases are returned', async () => {
|
|
||||||
const releaser = {
|
|
||||||
...mockReleaser,
|
|
||||||
allReleases: async function* () {
|
|
||||||
yield { data: [] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await findTagFromReleases(releaser, owner, repo, emptyTag);
|
|
||||||
|
|
||||||
assert.strictEqual(result, undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('error handling', () => {
|
|
||||||
it('handles 422 already_exists error gracefully', async () => {
|
|
||||||
const mockReleaser: Releaser = {
|
const mockReleaser: Releaser = {
|
||||||
getReleaseByTag: () => Promise.reject('Not implemented'),
|
createRelease: async () =>
|
||||||
createRelease: () =>
|
|
||||||
Promise.reject({
|
|
||||||
status: 422,
|
|
||||||
response: { data: { errors: [{ code: 'already_exists' }] } },
|
|
||||||
}),
|
|
||||||
updateRelease: () =>
|
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
data: {
|
data: {
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|
@ -259,24 +45,7 @@ describe('github', () => {
|
||||||
assets: [],
|
assets: [],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
allReleases: async function* () {
|
updateRelease: () => Promise.reject('Not implemented'),
|
||||||
yield {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
upload_url: 'test',
|
|
||||||
html_url: 'test',
|
|
||||||
tag_name: 'v1.0.0',
|
|
||||||
name: 'test',
|
|
||||||
body: 'test',
|
|
||||||
target_commitish: 'main',
|
|
||||||
draft: false,
|
|
||||||
prerelease: false,
|
|
||||||
assets: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
@ -300,7 +69,7 @@ describe('github', () => {
|
||||||
input_make_latest: undefined,
|
input_make_latest: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await release(config, mockReleaser, 1);
|
const result = await release(config, mockReleaser);
|
||||||
assert.ok(result);
|
assert.ok(result);
|
||||||
assert.equal(result.id, 1);
|
assert.equal(result.id, 1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
280
src/github.ts
280
src/github.ts
|
|
@ -27,8 +27,6 @@ export interface Release {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Releaser {
|
export interface Releaser {
|
||||||
getReleaseByTag(params: { owner: string; repo: string; tag: string }): Promise<{ data: Release }>;
|
|
||||||
|
|
||||||
createRelease(params: {
|
createRelease(params: {
|
||||||
owner: string;
|
owner: string;
|
||||||
repo: string;
|
repo: string;
|
||||||
|
|
@ -57,8 +55,6 @@ export interface Releaser {
|
||||||
generate_release_notes: boolean | undefined;
|
generate_release_notes: boolean | undefined;
|
||||||
make_latest: 'true' | 'false' | 'legacy' | undefined;
|
make_latest: 'true' | 'false' | 'legacy' | undefined;
|
||||||
}): Promise<{ data: Release }>;
|
}): Promise<{ data: Release }>;
|
||||||
|
|
||||||
allReleases(params: { owner: string; repo: string }): AsyncIterableIterator<{ data: Release[] }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GitHubReleaser implements Releaser {
|
export class GitHubReleaser implements Releaser {
|
||||||
|
|
@ -67,14 +63,6 @@ export class GitHubReleaser implements Releaser {
|
||||||
this.github = github;
|
this.github = github;
|
||||||
}
|
}
|
||||||
|
|
||||||
getReleaseByTag(params: {
|
|
||||||
owner: string;
|
|
||||||
repo: string;
|
|
||||||
tag: string;
|
|
||||||
}): Promise<{ data: Release }> {
|
|
||||||
return this.github.rest.repos.getReleaseByTag(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getReleaseNotes(params: {
|
async getReleaseNotes(params: {
|
||||||
owner: string;
|
owner: string;
|
||||||
repo: string;
|
repo: string;
|
||||||
|
|
@ -115,12 +103,28 @@ export class GitHubReleaser implements Releaser {
|
||||||
params.make_latest = undefined;
|
params.make_latest = undefined;
|
||||||
}
|
}
|
||||||
if (params.generate_release_notes) {
|
if (params.generate_release_notes) {
|
||||||
const releaseNotes = await this.getReleaseNotes(params);
|
try {
|
||||||
params.generate_release_notes = false;
|
const releaseNotes = await this.getReleaseNotes(params);
|
||||||
if (params.body) {
|
params.generate_release_notes = false;
|
||||||
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
|
if (params.body) {
|
||||||
} else {
|
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
|
||||||
params.body = releaseNotes.data.body;
|
} else {
|
||||||
|
params.body = releaseNotes.data.body;
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
// Handle GitHub API error when there are more than 10,000 commits
|
||||||
|
const status = error?.status || error?.response?.status;
|
||||||
|
const message = error?.message || error?.response?.data?.message || '';
|
||||||
|
if (status === 422 && (message.includes('10000') || message.includes('10000 results'))) {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ Unable to generate release notes: GitHub API limit exceeded (more than 10,000 commits since last release). Proceeding without generated release notes.`,
|
||||||
|
);
|
||||||
|
params.generate_release_notes = false;
|
||||||
|
// Continue with existing body or leave it empty
|
||||||
|
} else {
|
||||||
|
// Re-throw other errors
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
|
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
|
||||||
|
|
@ -148,24 +152,33 @@ export class GitHubReleaser implements Releaser {
|
||||||
params.make_latest = undefined;
|
params.make_latest = undefined;
|
||||||
}
|
}
|
||||||
if (params.generate_release_notes) {
|
if (params.generate_release_notes) {
|
||||||
const releaseNotes = await this.getReleaseNotes(params);
|
try {
|
||||||
params.generate_release_notes = false;
|
const releaseNotes = await this.getReleaseNotes(params);
|
||||||
if (params.body) {
|
params.generate_release_notes = false;
|
||||||
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
|
if (params.body) {
|
||||||
} else {
|
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
|
||||||
params.body = releaseNotes.data.body;
|
} else {
|
||||||
|
params.body = releaseNotes.data.body;
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
// Handle GitHub API error when there are more than 10,000 commits
|
||||||
|
const status = error?.status || error?.response?.status;
|
||||||
|
const message = error?.message || error?.response?.data?.message || '';
|
||||||
|
if (status === 422 && (message.includes('10000') || message.includes('10000 results'))) {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ Unable to generate release notes: GitHub API limit exceeded (more than 10,000 commits since last release). Proceeding without generated release notes.`,
|
||||||
|
);
|
||||||
|
params.generate_release_notes = false;
|
||||||
|
// Continue with existing body or leave it empty
|
||||||
|
} else {
|
||||||
|
// Re-throw other errors
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
|
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
|
||||||
return this.github.rest.repos.updateRelease(params);
|
return this.github.rest.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.rest.repos.listReleases.endpoint.merge(updatedParams),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const asset = (path: string): ReleaseAsset => {
|
export const asset = (path: string): ReleaseAsset => {
|
||||||
|
|
@ -241,206 +254,41 @@ export const upload = async (
|
||||||
export const release = async (
|
export const release = async (
|
||||||
config: Config,
|
config: Config,
|
||||||
releaser: Releaser,
|
releaser: Releaser,
|
||||||
maxRetries: number = 3,
|
|
||||||
): Promise<Release> => {
|
): Promise<Release> => {
|
||||||
if (maxRetries <= 0) {
|
|
||||||
console.log(`❌ Too many retries. Aborting...`);
|
|
||||||
throw new Error('Too many retries.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const [owner, repo] = config.github_repository.split('/');
|
const [owner, repo] = config.github_repository.split('/');
|
||||||
const tag =
|
const tag =
|
||||||
config.input_tag_name ||
|
config.input_tag_name ||
|
||||||
(isTag(config.github_ref) ? config.github_ref.replace('refs/tags/', '') : '');
|
(isTag(config.github_ref) ? config.github_ref.replace('refs/tags/', '') : '');
|
||||||
|
|
||||||
const discussion_category_name = config.input_discussion_category_name;
|
|
||||||
const generate_release_notes = config.input_generate_release_notes;
|
|
||||||
try {
|
|
||||||
const _release: Release | undefined = await findTagFromReleases(releaser, owner, repo, tag);
|
|
||||||
|
|
||||||
if (_release === undefined) {
|
|
||||||
return await createRelease(
|
|
||||||
tag,
|
|
||||||
config,
|
|
||||||
releaser,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
discussion_category_name,
|
|
||||||
generate_release_notes,
|
|
||||||
maxRetries,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let existingRelease: Release = _release!;
|
|
||||||
console.log(`Found release ${existingRelease.name} (with id=${existingRelease.id})`);
|
|
||||||
|
|
||||||
const release_id = existingRelease.id;
|
|
||||||
let target_commitish: string;
|
|
||||||
if (
|
|
||||||
config.input_target_commitish &&
|
|
||||||
config.input_target_commitish !== existingRelease.target_commitish
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
`Updating commit from "${existingRelease.target_commitish}" to "${config.input_target_commitish}"`,
|
|
||||||
);
|
|
||||||
target_commitish = config.input_target_commitish;
|
|
||||||
} else {
|
|
||||||
target_commitish = existingRelease.target_commitish;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tag_name = tag;
|
|
||||||
const name = config.input_name || existingRelease.name || tag;
|
|
||||||
// revisit: support a new body-concat-strategy input for accumulating
|
|
||||||
// body parts as a release gets updated. some users will likely want this while
|
|
||||||
// others won't previously this was duplicating content for most which
|
|
||||||
// no one wants
|
|
||||||
const workflowBody = releaseBody(config) || '';
|
|
||||||
const existingReleaseBody = existingRelease.body || '';
|
|
||||||
let body: string;
|
|
||||||
if (config.input_append_body && workflowBody && existingReleaseBody) {
|
|
||||||
body = existingReleaseBody + '\n' + workflowBody;
|
|
||||||
} else {
|
|
||||||
body = workflowBody || existingReleaseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
const draft = config.input_draft !== undefined ? config.input_draft : existingRelease.draft;
|
|
||||||
const prerelease =
|
|
||||||
config.input_prerelease !== undefined ? config.input_prerelease : existingRelease.prerelease;
|
|
||||||
|
|
||||||
const make_latest = config.input_make_latest;
|
|
||||||
|
|
||||||
const release = await releaser.updateRelease({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
release_id,
|
|
||||||
tag_name,
|
|
||||||
target_commitish,
|
|
||||||
name,
|
|
||||||
body,
|
|
||||||
draft,
|
|
||||||
prerelease,
|
|
||||||
discussion_category_name,
|
|
||||||
generate_release_notes,
|
|
||||||
make_latest,
|
|
||||||
});
|
|
||||||
return release.data;
|
|
||||||
} catch (error) {
|
|
||||||
if (error.status !== 404) {
|
|
||||||
console.log(
|
|
||||||
`⚠️ Unexpected error fetching GitHub release for tag ${config.github_ref}: ${error}`,
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await createRelease(
|
|
||||||
tag,
|
|
||||||
config,
|
|
||||||
releaser,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
discussion_category_name,
|
|
||||||
generate_release_notes,
|
|
||||||
maxRetries,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a release by tag name from all a repository's releases.
|
|
||||||
*
|
|
||||||
* @param releaser - The GitHub API wrapper for release operations
|
|
||||||
* @param owner - The owner of the repository
|
|
||||||
* @param repo - The name of the repository
|
|
||||||
* @param tag - The tag name to search for
|
|
||||||
* @returns The release with the given tag name, or undefined if no release with that tag name is found
|
|
||||||
*/
|
|
||||||
export async function findTagFromReleases(
|
|
||||||
releaser: Releaser,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
tag: string,
|
|
||||||
): Promise<Release | undefined> {
|
|
||||||
for await (const { data: releases } of releaser.allReleases({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
})) {
|
|
||||||
const release = releases.find((release) => release.tag_name === tag);
|
|
||||||
if (release) {
|
|
||||||
return release;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createRelease(
|
|
||||||
tag: string,
|
|
||||||
config: Config,
|
|
||||||
releaser: Releaser,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
discussion_category_name: string | undefined,
|
|
||||||
generate_release_notes: boolean | undefined,
|
|
||||||
maxRetries: number,
|
|
||||||
) {
|
|
||||||
const tag_name = tag;
|
const tag_name = tag;
|
||||||
const name = config.input_name || tag;
|
const name = config.input_name || tag;
|
||||||
const body = releaseBody(config);
|
const body = releaseBody(config);
|
||||||
const draft = config.input_draft;
|
const draft = config.input_draft;
|
||||||
const prerelease = config.input_prerelease;
|
const prerelease = config.input_prerelease;
|
||||||
const target_commitish = config.input_target_commitish;
|
const target_commitish = config.input_target_commitish;
|
||||||
|
const discussion_category_name = config.input_discussion_category_name;
|
||||||
|
const generate_release_notes = config.input_generate_release_notes;
|
||||||
const make_latest = config.input_make_latest;
|
const make_latest = config.input_make_latest;
|
||||||
|
|
||||||
let commitMessage: string = '';
|
let commitMessage: string = '';
|
||||||
if (target_commitish) {
|
if (target_commitish) {
|
||||||
commitMessage = ` using commit "${target_commitish}"`;
|
commitMessage = ` using commit "${target_commitish}"`;
|
||||||
}
|
}
|
||||||
console.log(`👩🏭 Creating new GitHub release for tag ${tag_name}${commitMessage}...`);
|
console.log(`👩🏭 Creating new GitHub release for tag ${tag_name}${commitMessage}...`);
|
||||||
try {
|
|
||||||
let release = await releaser.createRelease({
|
const release = await releaser.createRelease({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
tag_name,
|
tag_name,
|
||||||
name,
|
name,
|
||||||
body,
|
body,
|
||||||
draft,
|
draft,
|
||||||
prerelease,
|
prerelease,
|
||||||
target_commitish,
|
target_commitish,
|
||||||
discussion_category_name,
|
discussion_category_name,
|
||||||
generate_release_notes,
|
generate_release_notes,
|
||||||
make_latest,
|
make_latest,
|
||||||
});
|
});
|
||||||
return release.data;
|
|
||||||
} catch (error) {
|
return release.data;
|
||||||
// presume a race with competing matrix runs
|
};
|
||||||
console.log(`⚠️ GitHub release failed with status: ${error.status}`);
|
|
||||||
console.log(`${JSON.stringify(error.response.data)}`);
|
|
||||||
|
|
||||||
switch (error.status) {
|
|
||||||
case 403:
|
|
||||||
console.log(
|
|
||||||
'Skip retry — your GitHub token/PAT does not have the required permission to create a release',
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
|
|
||||||
case 404:
|
|
||||||
console.log('Skip retry - discussion category mismatch');
|
|
||||||
throw error;
|
|
||||||
|
|
||||||
case 422:
|
|
||||||
// Check if this is a race condition with "already_exists" error
|
|
||||||
const errorData = error.response?.data;
|
|
||||||
if (errorData?.errors?.[0]?.code === 'already_exists') {
|
|
||||||
console.log(
|
|
||||||
'⚠️ Release already exists (race condition detected), retrying to find and update existing release...',
|
|
||||||
);
|
|
||||||
// Don't throw - allow retry to find existing release
|
|
||||||
} else {
|
|
||||||
console.log('Skip retry - validation failed');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`retrying... (${maxRetries - 1} retries remaining)`);
|
|
||||||
return release(config, releaser, maxRetries - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue