mirror of
https://github.com/softprops/action-gh-release.git
synced 2026-03-15 09:20:54 -04:00
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:
15
README.md
15
README.md
@@ -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 |
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
10
dist/index.js
vendored
File diff suppressed because one or more lines are too long
176
src/github.ts
176
src/github.ts
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user