From 083a6546c8b15a5efcb288199c5b48f3c0d34357 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 26 Jul 2025 17:30:34 -0600 Subject: [PATCH 1/4] test(gha): update unit test for GitHub Actions output of `previous_version` ref: #512 --- .../cli/github_actions_output.py | 2 ++ .../cli/test_github_actions_output.py | 33 +++++++++++-------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/semantic_release/cli/github_actions_output.py b/src/semantic_release/cli/github_actions_output.py index ffe34987f..86b24e931 100644 --- a/src/semantic_release/cli/github_actions_output.py +++ b/src/semantic_release/cli/github_actions_output.py @@ -23,12 +23,14 @@ def __init__( version: Version | None = None, commit_sha: str | None = None, release_notes: str | None = None, + prev_version: Version | None = None, ) -> None: self._gh_client = gh_client self._released = released self._version = version self._commit_sha = commit_sha self._release_notes = release_notes + self._prev_version = prev_version @property def released(self) -> bool | None: diff --git a/tests/unit/semantic_release/cli/test_github_actions_output.py b/tests/unit/semantic_release/cli/test_github_actions_output.py index d650a18e5..7c4761d14 100644 --- a/tests/unit/semantic_release/cli/test_github_actions_output.py +++ b/tests/unit/semantic_release/cli/test_github_actions_output.py @@ -22,15 +22,17 @@ @pytest.mark.parametrize( - "version, is_prerelease", + "prev_version, version, released, is_prerelease", [ - ("1.2.3", False), - ("1.2.3-alpha.1", True), + ("1.2.2", "1.2.3", True, False), + ("1.2.2", "1.2.3-alpha.1", True, True), + ("1.2.2", "1.2.2", False, False), + ("1.2.2-alpha.1", "1.2.2-alpha.1", False, True), + (None, "1.2.3", True, False), ], ) -@pytest.mark.parametrize("released", (True, False)) def test_version_github_actions_output_format( - released: bool, version: str, is_prerelease: bool + released: bool, version: str, is_prerelease: bool, prev_version: str ): commit_sha = "0" * 40 # 40 zeroes to simulate a SHA-1 hash release_notes = dedent( @@ -43,15 +45,16 @@ def test_version_github_actions_output_format( expected_output = ( dedent( f"""\ - released={'true' if released else 'false'} - version={version} - tag=v{version} - is_prerelease={'true' if is_prerelease else 'false'} - link={BASE_VCS_URL}/releases/tag/v{version} - commit_sha={commit_sha} - """ + released={'true' if released else 'false'} + version={version} + tag=v{version} + is_prerelease={'true' if is_prerelease else 'false'} + link={BASE_VCS_URL}/releases/tag/v{version} + previous_version={prev_version or ""} + commit_sha={commit_sha} + """ ) - + f"release_notes< actual) @@ -106,6 +110,7 @@ def test_version_github_actions_output_writes_to_github_output_if_available( tmp_path: Path, ): mock_output_file = tmp_path / "action.out" + prev_version_str = "1.2.2" version_str = "1.2.3" commit_sha = "0" * 40 # 40 zeroes to simulate a SHA-1 hash release_notes = dedent( @@ -125,6 +130,7 @@ def test_version_github_actions_output_writes_to_github_output_if_available( released=True, commit_sha=commit_sha, release_notes=release_notes, + prev_version=Version.parse(prev_version_str), ).write_if_possible() with open(mock_output_file, encoding="utf-8", newline=os.linesep) as rfd: @@ -137,6 +143,7 @@ def test_version_github_actions_output_writes_to_github_output_if_available( assert f"{BASE_VCS_URL}/releases/tag/v{version_str}" == action_outputs["link"] assert f"v{version_str}" == action_outputs["tag"] assert commit_sha == action_outputs["commit_sha"] + assert prev_version_str == action_outputs["previous_version"] assert release_notes == action_outputs["release_notes"] From 7374add222cb7738304ac784d7046f09c23768e8 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 26 Jul 2025 17:45:44 -0600 Subject: [PATCH 2/4] test(gha): update e2e test for GitHub Actions output to check for `previous_version` output ref: #512 --- .../e2e/cmd_version/test_version_github_actions.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/e2e/cmd_version/test_version_github_actions.py b/tests/e2e/cmd_version/test_version_github_actions.py index 7c388e2da..ab86e556b 100644 --- a/tests/e2e/cmd_version/test_version_github_actions.py +++ b/tests/e2e/cmd_version/test_version_github_actions.py @@ -52,6 +52,9 @@ def test_version_writes_github_actions_output( all_versions = get_versions_from_repo_build_def(repo_def) latest_release_version = all_versions[-1] release_tag = tag_format_str.format(version=latest_release_version) + previous_version = ( + Version.parse(all_versions[-2]) if len(all_versions) > 1 else None + ) hvcs_client = cast("Github", get_hvcs_client_from_repo_def(repo_def)) repo_actions_per_version = split_repo_actions_by_release_tags( repo_definition=repo_def, @@ -66,12 +69,11 @@ def test_version_writes_github_actions_output( "is_prerelease": str( Version.parse(latest_release_version).is_prerelease ).lower(), + "previous_version": str(previous_version) if previous_version else "", "release_notes": generate_default_release_notes_from_def( version_actions=repo_actions_per_version[release_tag], hvcs=hvcs_client, - previous_version=( - Version.parse(all_versions[-2]) if len(all_versions) > 1 else None - ), + previous_version=previous_version, license_name=EXAMPLE_PROJECT_LICENSE, mask_initial_release=get_cfg_value_from_def( repo_def, "mask_initial_release" @@ -116,8 +118,6 @@ def test_version_writes_github_actions_output( assert expected_gha_output["tag"] == action_outputs["tag"] assert expected_gha_output["is_prerelease"] == action_outputs["is_prerelease"] assert expected_gha_output["link"] == action_outputs["link"] + assert expected_gha_output["previous_version"] == action_outputs["previous_version"] assert expected_gha_output["commit_sha"] == action_outputs["commit_sha"] - assert ( - expected_gha_output["release_notes"].encode() - == action_outputs["release_notes"].encode() - ) + assert expected_gha_output["release_notes"] == action_outputs["release_notes"] From 0e36a7c3c2c3e12b13046046b6b2dded5ccc9271 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 26 Jul 2025 17:52:41 -0600 Subject: [PATCH 3/4] feat(github-actions): add `previous_version` as a GitHub Actions output value ref: #512 --- src/semantic_release/cli/commands/version.py | 6 ++++++ .../cli/github_actions_output.py | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/semantic_release/cli/commands/version.py b/src/semantic_release/cli/commands/version.py index b144665b0..771ec273f 100644 --- a/src/semantic_release/cli/commands/version.py +++ b/src/semantic_release/cli/commands/version.py @@ -578,6 +578,12 @@ def version( # noqa: C901 if print_only or print_only_tag: return + # TODO: need a better way as this is inconsistent if releasing older version patches + if last_release := last_released(config.repo_dir, tag_format=config.tag_format): + # If we have a last release, we can set the previous version for the + # GitHub Actions output + gha_output.prev_version = last_release[1] + with Repo(str(runtime.repo_dir)) as git_repo: release_history = ReleaseHistory.from_git_history( repo=git_repo, diff --git a/src/semantic_release/cli/github_actions_output.py b/src/semantic_release/cli/github_actions_output.py index 86b24e931..fe2114aa5 100644 --- a/src/semantic_release/cli/github_actions_output.py +++ b/src/semantic_release/cli/github_actions_output.py @@ -38,7 +38,7 @@ def released(self) -> bool | None: @released.setter def released(self, value: bool) -> None: - if type(value) is not bool: + if not isinstance(value, bool): raise TypeError("output 'released' is boolean") self._released = value @@ -48,7 +48,7 @@ def version(self) -> Version | None: @version.setter def version(self, value: Version) -> None: - if type(value) is not Version: + if not isinstance(value, Version): raise TypeError("output 'released' should be a Version") self._version = value @@ -86,6 +86,18 @@ def release_notes(self, value: str) -> None: raise TypeError("output 'release_notes' should be a string") self._release_notes = value + @property + def prev_version(self) -> Version | None: + if not self.released: + return self.version + return self._prev_version if self._prev_version else None + + @prev_version.setter + def prev_version(self, value: Version) -> None: + if not isinstance(value, Version): + raise TypeError("output 'prev_version' should be a Version") + self._prev_version = value + def to_output_text(self) -> str: missing: set[str] = set() if self.version is None: @@ -108,6 +120,7 @@ def to_output_text(self) -> str: "tag": self.tag, "is_prerelease": str(self.is_prerelease).lower(), "link": self._gh_client.create_release_url(self.tag) if self.tag else "", + "previous_version": str(self.prev_version) if self.prev_version else "", "commit_sha": self.commit_sha if self.commit_sha else "", } From 737087af1982189472697c18f205a1b179bd3ba8 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 26 Jul 2025 17:56:04 -0600 Subject: [PATCH 4/4] docs(github-actions): add description of `previous_release` GitHub Action output --- action.yml | 5 +++++ .../automatic-releases/github-actions.rst | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/action.yml b/action.yml index 97f5a9111..0b9137bdf 100644 --- a/action.yml +++ b/action.yml @@ -135,6 +135,11 @@ outputs: The link to the release in the remote VCS, if a release was made. If no release was made, this will be an empty string. + previous_version: + description: | + The previous version before the release, if a release was or will be made. If no release is detected, + this will be the current version or an empty string if no previous version exists. + released: description: | "true" if a release was made, "false" otherwise diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index d21f2f351..bf144d6d6 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -552,6 +552,18 @@ Example when no release was made: ``""`` ---- +.. _gh_actions-psr-outputs-previous_version: + +``previous_version`` +"""""""""""""""""""" + +**Type:** ``string`` + +The previous version before the release, if a release was or will be made. If no release is detected, +this will be the current version or an empty string if no previous version exists. + +---- + .. _gh_actions-psr-outputs-released: ``released``