feat: support previous_tag for generate_release_notes (#372)

* feat: add generate from latest tag

* chore: refresh previous_tag docs and bundle

Signed-off-by: Rui Chen <rui@chenrui.dev>

---------

Signed-off-by: Rui Chen <rui@chenrui.dev>
Co-authored-by: Rui Chen <rui@chenrui.dev>
This commit is contained in:
Paulo Cesar
2026-03-15 02:17:21 -03:00
committed by GitHub
parent 1853d73993
commit 9312864490
7 changed files with 329 additions and 277 deletions

View File

@@ -173,6 +173,20 @@ jobs:
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
``` ```
When you use GitHub's built-in `generate_release_notes` support, you can optionally
pin the comparison base explicitly with `previous_tag`. This is useful when the default
comparison range does not match the release series you want to publish.
```yaml
- name: Release
uses: softprops/action-gh-release@v2
with:
tag_name: stage-2026-03-15
target_commitish: ${{ github.sha }}
previous_tag: prod-2026-03-01
generate_release_notes: true
```
### 💅 Customizing ### 💅 Customizing
#### inputs #### inputs
@@ -196,6 +210,7 @@ The following are optional as `step.with` keys
| `token` | String | Authorized GitHub token or PAT. Defaults to `${{ github.token }}` when omitted. A non-empty explicit token overrides `GITHUB_TOKEN`. Passing `""` treats the token as explicitly unset, so omit the input entirely or use an expression such as `${{ inputs.token || github.token }}` when wrapping this action in a composite action. | | `token` | String | Authorized GitHub token or PAT. Defaults to `${{ github.token }}` when omitted. A non-empty explicit token overrides `GITHUB_TOKEN`. Passing `""` treats the token as explicitly unset, so omit the input entirely or use an expression such as `${{ inputs.token || github.token }}` when wrapping this action in a composite action. |
| `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. See the [GitHub docs for this feature](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes) for more information | | `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. See the [GitHub docs for this feature](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes) for more information |
| `previous_tag` | String | Optional. When `generate_release_notes` is enabled, use this tag as GitHub's `previous_tag_name` comparison base. If omitted, GitHub chooses the comparison base automatically. |
| `append_body` | Boolean | Append to existing body instead of overwriting it | | `append_body` | Boolean | Append to existing body instead of overwriting it |
| `make_latest` | String | Specifies whether this release should be set as the latest release for the repository. Drafts and prereleases cannot be set as latest. Can be `true`, `false`, or `legacy`. Uses GitHub api defaults if not provided | | `make_latest` | String | Specifies whether this release should be set as the latest release for the repository. Drafts and prereleases cannot be set as latest. Can be `true`, `false`, or `legacy`. Uses GitHub api defaults if not provided |

View File

@@ -2,6 +2,7 @@ import {
asset, asset,
findTagFromReleases, findTagFromReleases,
finalizeRelease, finalizeRelease,
GitHubReleaser,
mimeOrDefault, mimeOrDefault,
release, release,
Release, Release,
@@ -32,6 +33,7 @@ describe('github', () => {
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_previous_tag: undefined,
input_append_body: false, input_append_body: false,
input_make_latest: undefined, input_make_latest: undefined,
}; };
@@ -146,6 +148,86 @@ describe('github', () => {
}); });
}); });
describe('GitHubReleaser', () => {
it('passes previous_tag_name to generateReleaseNotes and strips it from createRelease', async () => {
const generateReleaseNotes = vi.fn(async () => ({
data: {
name: 'Generated release',
body: "## What's Changed\n* Added support for previous_tag",
},
}));
const createRelease = vi.fn(async (params) => ({
data: {
id: 1,
upload_url: 'test',
html_url: 'test',
tag_name: params.tag_name,
name: params.name,
body: params.body,
target_commitish: params.target_commitish || 'main',
draft: params.draft ?? false,
prerelease: params.prerelease ?? false,
assets: [],
},
}));
const releaser = new GitHubReleaser({
rest: {
repos: {
generateReleaseNotes,
createRelease,
updateRelease: vi.fn(),
getReleaseByTag: vi.fn(),
listReleaseAssets: vi.fn(),
deleteReleaseAsset: vi.fn(),
deleteRelease: vi.fn(),
updateReleaseAsset: vi.fn(),
listReleases: {
endpoint: {
merge: vi.fn(),
},
},
},
},
paginate: {
iterator: vi.fn(),
},
request: vi.fn(),
} as any);
await releaser.createRelease({
owner: 'owner',
repo: 'repo',
tag_name: 'v1.0.0',
name: 'v1.0.0',
body: 'Intro',
draft: false,
prerelease: false,
target_commitish: 'abc123',
discussion_category_name: undefined,
generate_release_notes: true,
make_latest: undefined,
previous_tag_name: 'v0.9.0',
});
expect(generateReleaseNotes).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
tag_name: 'v1.0.0',
target_commitish: 'abc123',
previous_tag_name: 'v0.9.0',
});
expect(createRelease).toHaveBeenCalledWith(
expect.objectContaining({
tag_name: 'v1.0.0',
body: "Intro\n\n## What's Changed\n* Added support for previous_tag",
generate_release_notes: false,
}),
);
expect(createRelease.mock.calls[0][0]).not.toHaveProperty('previous_tag_name');
});
});
describe('finalizeRelease input_draft behavior', () => { describe('finalizeRelease input_draft behavior', () => {
const draftRelease: Release = { const draftRelease: Release = {
id: 1, id: 1,
@@ -340,6 +422,101 @@ describe('github', () => {
}); });
describe('error handling', () => { describe('error handling', () => {
it('passes previous_tag_name through when creating a release with generated notes', async () => {
const createReleaseSpy = vi.fn(async () => ({
data: {
id: 1,
upload_url: 'test',
html_url: 'test',
tag_name: 'v1.0.0',
name: 'test',
body: 'generated notes',
target_commitish: 'main',
draft: true,
prerelease: false,
assets: [],
},
}));
await release(
{
...config,
input_generate_release_notes: true,
input_previous_tag: 'v0.9.0',
},
{
getReleaseByTag: () => Promise.reject({ status: 404 }),
createRelease: createReleaseSpy,
updateRelease: () => Promise.reject('Not implemented'),
finalizeRelease: () => Promise.reject('Not implemented'),
allReleases: async function* () {
yield { data: [] };
},
listReleaseAssets: () => Promise.reject('Not implemented'),
deleteReleaseAsset: () => Promise.reject('Not implemented'),
deleteRelease: () => Promise.reject('Not implemented'),
updateReleaseAsset: () => Promise.reject('Not implemented'),
uploadReleaseAsset: () => Promise.reject('Not implemented'),
},
1,
);
expect(createReleaseSpy).toHaveBeenCalledWith(
expect.objectContaining({
tag_name: 'v1.0.0',
generate_release_notes: true,
previous_tag_name: 'v0.9.0',
}),
);
});
it('passes previous_tag_name through when updating a release with generated notes', async () => {
const existingRelease: Release = {
id: 1,
upload_url: 'test',
html_url: 'test',
tag_name: 'v1.0.0',
name: 'test',
body: 'existing body',
target_commitish: 'main',
draft: false,
prerelease: false,
assets: [],
};
const updateReleaseSpy = vi.fn(async () => ({ data: existingRelease }));
await release(
{
...config,
input_generate_release_notes: true,
input_previous_tag: 'v0.9.0',
},
{
getReleaseByTag: () => Promise.resolve({ data: existingRelease }),
createRelease: () => Promise.reject('Not implemented'),
updateRelease: updateReleaseSpy,
finalizeRelease: () => Promise.reject('Not implemented'),
allReleases: async function* () {
yield { data: [existingRelease] };
},
listReleaseAssets: () => Promise.reject('Not implemented'),
deleteReleaseAsset: () => Promise.reject('Not implemented'),
deleteRelease: () => Promise.reject('Not implemented'),
updateReleaseAsset: () => Promise.reject('Not implemented'),
uploadReleaseAsset: () => Promise.reject('Not implemented'),
},
1,
);
expect(updateReleaseSpy).toHaveBeenCalledWith(
expect.objectContaining({
release_id: existingRelease.id,
generate_release_notes: true,
previous_tag_name: 'v0.9.0',
}),
);
});
it('creates published prereleases without the forced draft-first path', async () => { it('creates published prereleases without the forced draft-first path', async () => {
const prereleaseConfig = { const prereleaseConfig = {
...config, ...config,

View File

@@ -174,6 +174,29 @@ describe('util', () => {
}); });
}); });
describe('parseConfig', () => { describe('parseConfig', () => {
const baseParsedConfig = {
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
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_previous_tag: undefined,
input_make_latest: undefined,
};
it('parses basic config', () => { it('parses basic config', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
@@ -186,27 +209,7 @@ describe('util', () => {
INPUT_TARGET_COMMITISH: '', INPUT_TARGET_COMMITISH: '',
INPUT_DISCUSSION_CATEGORY_NAME: '', INPUT_DISCUSSION_CATEGORY_NAME: '',
}), }),
{ baseParsedConfig,
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
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_make_latest: undefined,
},
); );
}); });
@@ -216,25 +219,8 @@ describe('util', () => {
INPUT_TARGET_COMMITISH: 'affa18ef97bc9db20076945705aba8c516139abd', INPUT_TARGET_COMMITISH: 'affa18ef97bc9db20076945705aba8c516139abd',
}), }),
{ {
github_ref: '', ...baseParsedConfig,
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_files: [],
input_overwrite_files: undefined,
input_preserve_order: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: 'affa18ef97bc9db20076945705aba8c516139abd', input_target_commitish: 'affa18ef97bc9db20076945705aba8c516139abd',
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
}, },
); );
}); });
@@ -244,25 +230,8 @@ describe('util', () => {
INPUT_DISCUSSION_CATEGORY_NAME: 'releases', INPUT_DISCUSSION_CATEGORY_NAME: 'releases',
}), }),
{ {
github_ref: '', ...baseParsedConfig,
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_files: [],
input_preserve_order: undefined,
input_name: undefined,
input_overwrite_files: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: 'releases', input_discussion_category_name: 'releases',
input_generate_release_notes: false,
input_make_latest: undefined,
}, },
); );
}); });
@@ -273,25 +242,20 @@ describe('util', () => {
INPUT_GENERATE_RELEASE_NOTES: 'true', INPUT_GENERATE_RELEASE_NOTES: 'true',
}), }),
{ {
github_ref: '', ...baseParsedConfig,
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
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: true, input_generate_release_notes: true,
input_make_latest: undefined, },
);
});
it('supports an explicit previous tag for release notes generation', () => {
assert.deepStrictEqual(
parseConfig({
INPUT_PREVIOUS_TAG: ' v1.2.3 ',
}),
{
...baseParsedConfig,
input_previous_tag: 'v1.2.3',
}, },
); );
}); });
@@ -306,25 +270,11 @@ describe('util', () => {
INPUT_TOKEN: 'input-token', INPUT_TOKEN: 'input-token',
}), }),
{ {
github_ref: '', ...baseParsedConfig,
github_repository: '',
github_token: 'input-token', github_token: 'input-token',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: false, input_draft: false,
input_prerelease: true, input_prerelease: true,
input_preserve_order: true, input_preserve_order: true,
input_files: [],
input_overwrite_files: undefined,
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_make_latest: undefined,
}, },
); );
}); });
@@ -335,25 +285,8 @@ describe('util', () => {
INPUT_TOKEN: ' ', INPUT_TOKEN: ' ',
}), }),
{ {
github_ref: '', ...baseParsedConfig,
github_repository: '',
github_token: 'env-token', github_token: 'env-token',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
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_make_latest: undefined,
}, },
); );
}); });
@@ -365,25 +298,10 @@ describe('util', () => {
INPUT_TOKEN: 'input-token', INPUT_TOKEN: 'input-token',
}), }),
{ {
github_ref: '', ...baseParsedConfig,
github_repository: '',
github_token: 'input-token', github_token: 'input-token',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: false, input_draft: false,
input_prerelease: true, input_prerelease: true,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
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_make_latest: undefined,
}, },
); );
}); });
@@ -394,25 +312,9 @@ describe('util', () => {
INPUT_PRERELEASE: 'true', INPUT_PRERELEASE: 'true',
}), }),
{ {
github_ref: '', ...baseParsedConfig,
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: false, input_draft: false,
input_prerelease: true, input_prerelease: true,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
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_make_latest: undefined,
}, },
); );
}); });
@@ -422,24 +324,7 @@ describe('util', () => {
INPUT_MAKE_LATEST: 'false', INPUT_MAKE_LATEST: 'false',
}), }),
{ {
github_ref: '', ...baseParsedConfig,
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_name: undefined,
input_overwrite_files: 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_make_latest: 'false', input_make_latest: 'false',
}, },
); );
@@ -450,25 +335,8 @@ describe('util', () => {
INPUT_APPEND_BODY: 'true', INPUT_APPEND_BODY: 'true',
}), }),
{ {
github_ref: '', ...baseParsedConfig,
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: true, input_append_body: true,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
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_make_latest: undefined,
}, },
); );
}); });

View File

@@ -53,6 +53,10 @@ 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
previous_tag:
description: "Optional. When generate_release_notes is enabled, use this tag as GitHub's previous_tag_name comparison base. If omitted, GitHub chooses the comparison base automatically."
required: false
default: ""
append_body: append_body:
description: "Append to existing body instead of overwriting it. Default is false." description: "Append to existing body instead of overwriting it. Default is false."
required: false required: false

10
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -31,37 +31,40 @@ export interface ReleaseResult {
created: boolean; created: boolean;
} }
type ReleaseNotesParams = {
owner: string;
repo: string;
tag_name: string;
target_commitish: string | undefined;
previous_tag_name?: string;
};
type ReleaseMutationParams = {
owner: string;
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
target_commitish: string | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
previous_tag_name?: string;
};
export interface Releaser { export interface Releaser {
getReleaseByTag(params: { owner: string; repo: string; tag: string }): Promise<{ data: Release }>; getReleaseByTag(params: { owner: string; repo: string; tag: string }): Promise<{ data: Release }>;
createRelease(params: { createRelease(params: ReleaseMutationParams): Promise<{ data: Release }>;
owner: string;
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
target_commitish: string | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }>;
updateRelease(params: { updateRelease(
owner: string; params: ReleaseMutationParams & {
repo: string; release_id: number;
release_id: number; target_commitish: string;
tag_name: string; },
target_commitish: string; ): Promise<{ data: Release }>;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }>;
finalizeRelease(params: { finalizeRelease(params: {
owner: string; owner: string;
@@ -113,12 +116,7 @@ export class GitHubReleaser implements Releaser {
return this.github.rest.repos.getReleaseByTag(params); return this.github.rest.repos.getReleaseByTag(params);
} }
async getReleaseNotes(params: { async getReleaseNotes(params: ReleaseNotesParams): Promise<{
owner: string;
repo: string;
tag_name: string;
target_commitish: string | undefined;
}): Promise<{
data: { data: {
name: string; name: string;
body: string; body: string;
@@ -127,75 +125,55 @@ export class GitHubReleaser implements Releaser {
return await this.github.rest.repos.generateReleaseNotes(params); return await this.github.rest.repos.generateReleaseNotes(params);
} }
private async prepareReleaseMutation<T extends ReleaseMutationParams>(
params: T,
): Promise<Omit<T, 'previous_tag_name'>> {
const { previous_tag_name, ...releaseParams } = params;
if (
typeof releaseParams.make_latest === 'string' &&
!['true', 'false', 'legacy'].includes(releaseParams.make_latest)
) {
releaseParams.make_latest = undefined;
}
if (releaseParams.generate_release_notes) {
const releaseNotes = await this.getReleaseNotes({
owner: releaseParams.owner,
repo: releaseParams.repo,
tag_name: releaseParams.tag_name,
target_commitish: releaseParams.target_commitish,
previous_tag_name,
});
releaseParams.generate_release_notes = false;
if (releaseParams.body) {
releaseParams.body = `${releaseParams.body}\n\n${releaseNotes.data.body}`;
} else {
releaseParams.body = releaseNotes.data.body;
}
}
releaseParams.body = releaseParams.body
? this.truncateReleaseNotes(releaseParams.body)
: undefined;
return releaseParams;
}
truncateReleaseNotes(input: string): string { truncateReleaseNotes(input: string): string {
// release notes can be a maximum of 125000 characters // release notes can be a maximum of 125000 characters
const githubNotesMaxCharLength = 125000; const githubNotesMaxCharLength = 125000;
return input.substring(0, githubNotesMaxCharLength - 1); return input.substring(0, githubNotesMaxCharLength - 1);
} }
async createRelease(params: { async createRelease(params: ReleaseMutationParams): Promise<{ data: Release }> {
owner: string; return this.github.rest.repos.createRelease(await this.prepareReleaseMutation(params));
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
target_commitish: string | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }> {
if (
typeof params.make_latest === 'string' &&
!['true', 'false', 'legacy'].includes(params.make_latest)
) {
params.make_latest = undefined;
}
if (params.generate_release_notes) {
const releaseNotes = await this.getReleaseNotes(params);
params.generate_release_notes = false;
if (params.body) {
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
} else {
params.body = releaseNotes.data.body;
}
}
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
return this.github.rest.repos.createRelease(params);
} }
async updateRelease(params: { async updateRelease(
owner: string; params: ReleaseMutationParams & {
repo: string; release_id: number;
release_id: number; target_commitish: string;
tag_name: string; },
target_commitish: string; ): Promise<{ data: Release }> {
name: string; return this.github.rest.repos.updateRelease(await this.prepareReleaseMutation(params));
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }> {
if (
typeof params.make_latest === 'string' &&
!['true', 'false', 'legacy'].includes(params.make_latest)
) {
params.make_latest = undefined;
}
if (params.generate_release_notes) {
const releaseNotes = await this.getReleaseNotes(params);
params.generate_release_notes = false;
if (params.body) {
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
} else {
params.body = releaseNotes.data.body;
}
}
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
return this.github.rest.repos.updateRelease(params);
} }
async finalizeRelease(params: { async finalizeRelease(params: {
@@ -425,6 +403,11 @@ 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 previous_tag_name = config.input_previous_tag;
if (generate_release_notes && previous_tag_name) {
console.log(`📝 Generating release notes using previous tag ${previous_tag_name}`);
}
try { try {
const _release: Release | undefined = await findTagFromReleases(releaser, owner, repo, tag); const _release: Release | undefined = await findTagFromReleases(releaser, owner, repo, tag);
@@ -438,6 +421,7 @@ export const release = async (
discussion_category_name, discussion_category_name,
generate_release_notes, generate_release_notes,
maxRetries, maxRetries,
previous_tag_name,
); );
} }
@@ -491,6 +475,7 @@ export const release = async (
discussion_category_name, discussion_category_name,
generate_release_notes, generate_release_notes,
make_latest, make_latest,
previous_tag_name,
}); });
return { return {
release: release.data, release: release.data,
@@ -513,6 +498,7 @@ export const release = async (
discussion_category_name, discussion_category_name,
generate_release_notes, generate_release_notes,
maxRetries, maxRetries,
previous_tag_name,
); );
} }
}; };
@@ -796,6 +782,7 @@ async function createRelease(
discussion_category_name: string | undefined, discussion_category_name: string | undefined,
generate_release_notes: boolean | undefined, generate_release_notes: boolean | undefined,
maxRetries: number, maxRetries: number,
previous_tag_name: string | undefined,
): Promise<ReleaseResult> { ): Promise<ReleaseResult> {
const tag_name = tag; const tag_name = tag;
const name = config.input_name || tag; const name = config.input_name || tag;
@@ -822,6 +809,7 @@ async function createRelease(
discussion_category_name, discussion_category_name,
generate_release_notes, generate_release_notes,
make_latest, make_latest,
previous_tag_name,
}); });
const canonicalRelease = await canonicalizeCreatedRelease( const canonicalRelease = await canonicalizeCreatedRelease(
releaser, releaser,

View File

@@ -23,6 +23,7 @@ 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_previous_tag?: string;
input_append_body?: boolean; input_append_body?: boolean;
input_make_latest: 'true' | 'false' | 'legacy' | undefined; input_make_latest: 'true' | 'false' | 'legacy' | undefined;
} }
@@ -114,6 +115,7 @@ 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: env.INPUT_DISCUSSION_CATEGORY_NAME || undefined, input_discussion_category_name: 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_previous_tag: env.INPUT_PREVIOUS_TAG?.trim() || undefined,
input_append_body: env.INPUT_APPEND_BODY == 'true', input_append_body: env.INPUT_APPEND_BODY == 'true',
input_make_latest: parseMakeLatest(env.INPUT_MAKE_LATEST), input_make_latest: parseMakeLatest(env.INPUT_MAKE_LATEST),
}; };