python-semantic-release 9.5.0__py3-none-any.whl → 9.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {python_semantic_release-9.5.0.dist-info → python_semantic_release-9.7.0.dist-info}/METADATA +3 -3
  2. {python_semantic_release-9.5.0.dist-info → python_semantic_release-9.7.0.dist-info}/RECORD +26 -25
  3. semantic_release/__init__.py +1 -1
  4. semantic_release/changelog/context.py +3 -1
  5. semantic_release/cli/commands/changelog.py +2 -1
  6. semantic_release/cli/commands/publish.py +8 -0
  7. semantic_release/cli/commands/version.py +113 -43
  8. semantic_release/cli/config.py +94 -19
  9. semantic_release/commit_parser/_base.py +13 -4
  10. semantic_release/commit_parser/angular.py +7 -2
  11. semantic_release/commit_parser/emoji.py +5 -0
  12. semantic_release/commit_parser/scipy.py +12 -13
  13. semantic_release/commit_parser/tag.py +5 -0
  14. semantic_release/errors.py +22 -1
  15. semantic_release/hvcs/__init__.py +10 -1
  16. semantic_release/hvcs/_base.py +32 -109
  17. semantic_release/hvcs/bitbucket.py +87 -140
  18. semantic_release/hvcs/gitea.py +61 -71
  19. semantic_release/hvcs/github.py +120 -156
  20. semantic_release/hvcs/gitlab.py +34 -76
  21. semantic_release/hvcs/remote_hvcs_base.py +193 -0
  22. {python_semantic_release-9.5.0.dist-info → python_semantic_release-9.7.0.dist-info}/AUTHORS.rst +0 -0
  23. {python_semantic_release-9.5.0.dist-info → python_semantic_release-9.7.0.dist-info}/LICENSE +0 -0
  24. {python_semantic_release-9.5.0.dist-info → python_semantic_release-9.7.0.dist-info}/WHEEL +0 -0
  25. {python_semantic_release-9.5.0.dist-info → python_semantic_release-9.7.0.dist-info}/entry_points.txt +0 -0
  26. {python_semantic_release-9.5.0.dist-info → python_semantic_release-9.7.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-semantic-release
3
- Version: 9.5.0
3
+ Version: 9.7.0
4
4
  Summary: Automatic Semantic Versioning for Python projects
5
5
  Author-email: Rolf Erik Lekang <me@rolflekang.com>
6
6
  License: MIT
@@ -35,14 +35,14 @@ Requires-Dist: shellingham ~=1.5
35
35
  Provides-Extra: dev
36
36
  Requires-Dist: pre-commit ~=3.5 ; extra == 'dev'
37
37
  Requires-Dist: tox ~=4.11 ; extra == 'dev'
38
- Requires-Dist: ruff ==0.4.1 ; extra == 'dev'
38
+ Requires-Dist: ruff ==0.4.2 ; extra == 'dev'
39
39
  Provides-Extra: docs
40
40
  Requires-Dist: Sphinx ~=6.0 ; extra == 'docs'
41
41
  Requires-Dist: sphinxcontrib-apidoc ==0.5.0 ; extra == 'docs'
42
42
  Requires-Dist: sphinx-autobuild ==2024.2.4 ; extra == 'docs'
43
43
  Requires-Dist: furo ~=2024.1 ; extra == 'docs'
44
44
  Provides-Extra: mypy
45
- Requires-Dist: mypy ==1.9.0 ; extra == 'mypy'
45
+ Requires-Dist: mypy ==1.10.0 ; extra == 'mypy'
46
46
  Requires-Dist: types-requests ~=2.31.0 ; extra == 'mypy'
47
47
  Provides-Extra: test
48
48
  Requires-Dist: coverage[toml] ~=7.0 ; extra == 'test'
@@ -1,43 +1,44 @@
1
- semantic_release/__init__.py,sha256=OKGFjDLUMtF4yJFzPOCIxLjmnv_T7QJWrmqcCinoIDA,870
1
+ semantic_release/__init__.py,sha256=YntTmtPFc-fYThwdkIDrGo6S89Mz4851VAoDyOj_zoo,870
2
2
  semantic_release/__main__.py,sha256=vXOARLV7KHeVXgpOMU0Q-8wovpdsOrtTsycxulI5xIE,106
3
3
  semantic_release/const.py,sha256=Z1o2QNh60wSLeF-_1TemMBjU3ZXbV0XghnUFsbTVfOs,831
4
4
  semantic_release/enums.py,sha256=D5B_reQGGKQQT22HO5PUtvn2Bok3fkht6TfJtXkmAUg,1020
5
- semantic_release/errors.py,sha256=070p5fRe4l2BYv1zbTZjnM5YwRcJmi9BVa8TdMFzvf0,1159
5
+ semantic_release/errors.py,sha256=2hrKuCRWN5O_0z-C2Owf6gcTUe2MpP4Y27nHoUzJBng,1695
6
6
  semantic_release/helpers.py,sha256=d1jOX0SNyqPc_3wr14xR25FfpqhMd4Ev7MNBOWlScc0,5581
7
7
  semantic_release/changelog/__init__.py,sha256=Bg6Xe5Vt32rWoMscW-hd4sUwiZqzWmsg4CD1EhMesMY,262
8
- semantic_release/changelog/context.py,sha256=ItSz5Bon2mcJkLxyQZzBTukN0zEdcaVQ4iXvz3jGCLc,986
8
+ semantic_release/changelog/context.py,sha256=YAtv_svSbRsRUqHfTbmfJRpE5xkOEUxmYaBINYJyXLE,1051
9
9
  semantic_release/changelog/release_history.py,sha256=ShzZpxTjhxka6TMbzOzYzG3EuCH8j3xjUQzll-FKcCo,6675
10
10
  semantic_release/changelog/template.py,sha256=JqZcjdozM9-059Sa8PfiHxYNeyZFa0jxk_beyRLv5sU,4640
11
11
  semantic_release/cli/__init__.py,sha256=gwAYhZO-4TR0tKCkQ4GqutWsL--0CII2_dcbrElI4M4,419
12
12
  semantic_release/cli/common.py,sha256=cVQUI7Uaac_nJ7VEXuJ7Q9pVJpuMVRvE7bBl_i96g20,1439
13
- semantic_release/cli/config.py,sha256=9NhaxZ1F1ux-8Qu7_KeE7kLH3eCsllkPV4Hy5YI1tv4,17574
13
+ semantic_release/cli/config.py,sha256=1q7tNFC4Re8gcctZyIGSKcj6qZ59W5mx9ed2yk5bgSA,20389
14
14
  semantic_release/cli/const.py,sha256=S8oNNpSaKdZxcAs7PAKEjQHic0czoJBGd7XHo57FOzw,39
15
15
  semantic_release/cli/github_actions_output.py,sha256=Wy4jEIpSFgoMyJqq4FD7scIpgovivutFLZ7Fs4y2CUY,2110
16
16
  semantic_release/cli/masking_filter.py,sha256=DxqjiJyABlzwwwZ1r8JGQpb6QrF00StJFm0-2-s5Fv0,3071
17
17
  semantic_release/cli/util.py,sha256=FyXaBkeL7nXKjy3X9rQLEwvn7p46xPekp2V8Z-5MVrk,3755
18
18
  semantic_release/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- semantic_release/cli/commands/changelog.py,sha256=Tk9DprnY9mDQXSE1krQd8oILn3W6dKN_stWx9EbLjhU,4087
19
+ semantic_release/cli/commands/changelog.py,sha256=rNZwWucEbR2BjbmgHQa9R1nJk5Fu0Ph3U3wh-daH42Q,4197
20
20
  semantic_release/cli/commands/cli_context.py,sha256=fOLTUKb4UnZGQ6zVhftuvdfZ7fK7bDDIvj7Ud5yU98c,3443
21
21
  semantic_release/cli/commands/generate_config.py,sha256=2xZOu3NpyhBp0pWr7d8ugKl_kjqQgpSsSMHq5wHTfrE,1699
22
22
  semantic_release/cli/commands/main.py,sha256=LuL-O0LGonI5fQTZn0AUtYv2Ei_CYyNqYHMF_wN4InI,2900
23
- semantic_release/cli/commands/publish.py,sha256=_pz7rKx65tCH7J2uyWiBghQ61MmmIbxhF9bE4AkE0N8,1703
24
- semantic_release/cli/commands/version.py,sha256=H_I4AmZzBQdMzYejRKgCyvMtx55omzpSHJFWMz-uQRc,23015
23
+ semantic_release/cli/commands/publish.py,sha256=ZA7DuEGxZdIT9wUwP1S24qi1RdcKV-1FnYci-PvyXIg,1958
24
+ semantic_release/cli/commands/version.py,sha256=liG9q4f_pH0ISGMODJ-qYbIv-OoLggfOx6RCOnA7ow8,25912
25
25
  semantic_release/commit_parser/__init__.py,sha256=cv5HFBdw7OJd4Laj4Ex8ZZ5Tml8GwXgQcXW6Pasr2Ao,615
26
- semantic_release/commit_parser/_base.py,sha256=aDS0dhFosT5i11tA5Ez8BTeikk6jwXjSEpJnJC5D2cs,2609
27
- semantic_release/commit_parser/angular.py,sha256=bS7Wrzpg_6zFil5Y4GIZkeGDPhvWQdTlFdWTtGVcpp0,4393
28
- semantic_release/commit_parser/emoji.py,sha256=6cFnqpvtjtxlwDYc8oB9y41RPqFGGD8PbqiAA53SVac,3289
29
- semantic_release/commit_parser/scipy.py,sha256=AaAtQYUsG_Y29YIgDVISl5mljBCkG8zyzUkn_3HYGeM,5777
30
- semantic_release/commit_parser/tag.py,sha256=0Y8GTRclyoLMhyDRrcMU8JKTM4lsbfpJbjqZ7qz5ifE,3194
26
+ semantic_release/commit_parser/_base.py,sha256=D2dY8dALV3QPK0XOLZru9btMW5dlx9YSmDIe3v5T0QA,3004
27
+ semantic_release/commit_parser/angular.py,sha256=ERWeTBTFNIBOaTIopY4Vff0KbeFwrSQWbADe3smVW-8,4579
28
+ semantic_release/commit_parser/emoji.py,sha256=M6zgqsXqegLK5FxZGyfsOPM3vOsJkPKVAjnGATOhwa4,3452
29
+ semantic_release/commit_parser/scipy.py,sha256=uRNLSXH4LApxm8xSyUil23WVO3C8VEFRCnwTIKZbyQk,5879
30
+ semantic_release/commit_parser/tag.py,sha256=SAop9INQvKOwktNmVypKFngd6xHmY_XZrLUWChUjz84,3353
31
31
  semantic_release/commit_parser/token.py,sha256=UxYAwEylPvp4x39RqNA9ITcgkaSRDEH7A5XDDJ3tdiM,1505
32
32
  semantic_release/commit_parser/util.py,sha256=vLcVDErZrExM55jMffos0hyMbNVQoJ-PeeVDG1Ej51I,730
33
33
  semantic_release/data/templates/CHANGELOG.md.j2,sha256=GB3YJwgVrhlcb-g5ESdEh_Gsysb_Texxv-Ip2LjulC0,1066
34
34
  semantic_release/data/templates/release_notes.md.j2,sha256=27uIHgG6iYmqdMaoWFP5LJRZcrO-3paqFzHHHsGpXXE,465
35
- semantic_release/hvcs/__init__.py,sha256=ulesZslSG4Lu8Iz3YzOrPXpdgvgbADq_MV17B-bSYQA,379
36
- semantic_release/hvcs/_base.py,sha256=qjjlU3rI-sOhIY_FfXsZ9OQGthNYxgzpL4PhlGSXEuA,5232
37
- semantic_release/hvcs/bitbucket.py,sha256=6vGOcRG7WexxG8SzLcWDIzp7zNpB0ruDLUTQ9GE2jGM,11004
38
- semantic_release/hvcs/gitea.py,sha256=OTThBTZ93xUP9am7tNIE0UVchhJwG9ASvI6zuiG8oLE,11450
39
- semantic_release/hvcs/github.py,sha256=DI0fK_dhVAzwd6S80zZMMVIAsz-b0zTGca-SMVJIUyE,19965
40
- semantic_release/hvcs/gitlab.py,sha256=qmowL22DCNOFa7RwmMTmNmno3BS0dyNYT9L36ujgZ8A,7409
35
+ semantic_release/hvcs/__init__.py,sha256=JwoaLOF-12L-OBo_9-tOXXhdiHKeVungA9865to2oZk,494
36
+ semantic_release/hvcs/_base.py,sha256=9-iTqTPSbiEevKbCBP9K2hq4c-2T4wPbeLWe-kAxBzo,2607
37
+ semantic_release/hvcs/bitbucket.py,sha256=tMpl69CbZSQ8cdgiqtIzf7WSz8OQwDInGpoq-9tQr3k,9216
38
+ semantic_release/hvcs/gitea.py,sha256=CSItRSJU39hohO3oE-ohX65Er_s2nVrDqvfc7KQtmv8,11152
39
+ semantic_release/hvcs/github.py,sha256=34ZMGttmqcTcY6_D-SRn-HxUJxNT4WwBNEwYlsHC420,18287
40
+ semantic_release/hvcs/gitlab.py,sha256=JtHWBaNGSMB8MTiznEhvuMm_M8lLceZPaGndzOx39Vk,6249
41
+ semantic_release/hvcs/remote_hvcs_base.py,sha256=plZkc5cSaiTxgIR5HhlEWrYoyWzzSXr-Jw3aXbMK_iA,6055
41
42
  semantic_release/hvcs/token_auth.py,sha256=ZjT56-NIPB4OKIt1qwHCu1TavXnrWFIBl9ARlg56hgU,663
42
43
  semantic_release/hvcs/util.py,sha256=PNKXApgL2np9lpnKjAbo77toRgElLPXrbS1q7621waI,2886
43
44
  semantic_release/version/__init__.py,sha256=CLhtGQry9dLIij5XyRa9ZevxU_1p8tjMTSQ-K_GMpWM,270
@@ -45,10 +46,10 @@ semantic_release/version/algorithm.py,sha256=-9ulTy1z4u-jUvbc-FrvEdFL7Omc3O3HhaP
45
46
  semantic_release/version/declaration.py,sha256=f6Ld7hIhrqvDrRBapJHr-KDimuyo-4IG8009Zu9BIgU,7357
46
47
  semantic_release/version/translator.py,sha256=P1noIsVBn8u6zNOFjG0xKYOWapxqf_PHSMvMeLJ9kXg,3050
47
48
  semantic_release/version/version.py,sha256=6PCtSbLP88U1daoxnCwHc--YguZo4waGNLqJ5JfeczE,14175
48
- python_semantic_release-9.5.0.dist-info/AUTHORS.rst,sha256=XOReVvpymEFUPsS2QPH97jlfJBVrxwS2eu8-jVAe4gk,230
49
- python_semantic_release-9.5.0.dist-info/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
50
- python_semantic_release-9.5.0.dist-info/METADATA,sha256=7RR8GcLU8LGCvbkMVyzcxDjom5Fz8xP4lxLMDi8rHLM,5335
51
- python_semantic_release-9.5.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
52
- python_semantic_release-9.5.0.dist-info/entry_points.txt,sha256=qojUBx_Ywf4u7KgP6NWlDaBDymZVUGQ7m5wF2duC2F0,95
53
- python_semantic_release-9.5.0.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
54
- python_semantic_release-9.5.0.dist-info/RECORD,,
49
+ python_semantic_release-9.7.0.dist-info/AUTHORS.rst,sha256=XOReVvpymEFUPsS2QPH97jlfJBVrxwS2eu8-jVAe4gk,230
50
+ python_semantic_release-9.7.0.dist-info/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
51
+ python_semantic_release-9.7.0.dist-info/METADATA,sha256=jiFhqNG1gByE9-iM8EOMljPsuxhc-XIBXcugDJ9tJ1M,5336
52
+ python_semantic_release-9.7.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
53
+ python_semantic_release-9.7.0.dist-info/entry_points.txt,sha256=qojUBx_Ywf4u7KgP6NWlDaBDymZVUGQ7m5wF2duC2F0,95
54
+ python_semantic_release-9.7.0.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
55
+ python_semantic_release-9.7.0.dist-info/RECORD,,
@@ -24,7 +24,7 @@ from semantic_release.version import (
24
24
  tags_and_versions,
25
25
  )
26
26
 
27
- __version__ = "9.5.0"
27
+ __version__ = "9.7.0"
28
28
 
29
29
 
30
30
  def setup_hook(argv: list[str]) -> None:
@@ -14,6 +14,7 @@ if TYPE_CHECKING:
14
14
  class ChangelogContext:
15
15
  repo_name: str
16
16
  repo_owner: str
17
+ hvcs_type: str
17
18
  history: ReleaseHistory
18
19
  filters: tuple[Callable[..., Any], ...] = ()
19
20
 
@@ -31,5 +32,6 @@ def make_changelog_context(
31
32
  repo_name=hvcs_client.repo_name,
32
33
  repo_owner=hvcs_client.owner,
33
34
  history=release_history,
34
- filters=(hvcs_client.pull_request_url, hvcs_client.commit_hash_url),
35
+ hvcs_type=hvcs_client.__class__.__name__.lower(),
36
+ filters=(*hvcs_client.get_changelog_context_filters(),),
35
37
  )
@@ -14,6 +14,7 @@ from semantic_release.cli.common import (
14
14
  render_release_notes,
15
15
  )
16
16
  from semantic_release.cli.util import noop_report
17
+ from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from semantic_release.cli.commands.cli_context import CliContextObj
@@ -79,7 +80,7 @@ def changelog(cli_ctx: CliContextObj, release_tag: str | None = None) -> None:
79
80
  else:
80
81
  recursive_render(template_dir, environment=env, _root_dir=repo.working_dir)
81
82
 
82
- if release_tag:
83
+ if release_tag and isinstance(hvcs_client, RemoteHvcsBase):
83
84
  if runtime.global_cli_options.noop:
84
85
  noop_report(
85
86
  f"would have posted changelog to the release for tag {release_tag}"
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
6
6
  import click
7
7
 
8
8
  from semantic_release.cli.util import noop_report
9
+ from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase
9
10
  from semantic_release.version import tags_and_versions
10
11
 
11
12
  if TYPE_CHECKING:
@@ -45,6 +46,13 @@ def publish(cli_ctx: CliContextObj, tag: str = "latest") -> None:
45
46
  f"No tags found with format {translator.tag_format!r}, couldn't "
46
47
  "identify latest version"
47
48
  )
49
+
50
+ if not isinstance(hvcs_client, RemoteHvcsBase):
51
+ log.info(
52
+ "Remote does not support artifact upload. Exiting with no action taken..."
53
+ )
54
+ ctx.exit(0)
55
+
48
56
  if runtime.global_cli_options.noop:
49
57
  noop_report(
50
58
  "would have uploaded files matching any of the globs "
@@ -25,12 +25,13 @@ from semantic_release.cli.util import indented, noop_report, rprint
25
25
  from semantic_release.const import DEFAULT_SHELL, DEFAULT_VERSION
26
26
  from semantic_release.enums import LevelBump
27
27
  from semantic_release.errors import UnexpectedResponse
28
+ from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase
28
29
  from semantic_release.version import Version, next_version, tags_and_versions
29
30
 
30
31
  log = logging.getLogger(__name__)
31
32
 
32
33
  if TYPE_CHECKING: # pragma: no cover
33
- from typing import ContextManager, Iterable
34
+ from typing import ContextManager, Iterable, Mapping
34
35
 
35
36
  from git import Repo
36
37
  from git.refs.tag import Tag
@@ -41,7 +42,7 @@ if TYPE_CHECKING: # pragma: no cover
41
42
 
42
43
 
43
44
  def is_forced_prerelease(
44
- force_prerelease: bool, force_level: str | None, prerelease: bool
45
+ as_prerelease: bool, forced_level_bump: LevelBump | None, prerelease: bool
45
46
  ) -> bool:
46
47
  """
47
48
  Determine if this release is forced to have prerelease on/off.
@@ -50,8 +51,17 @@ def is_forced_prerelease(
50
51
  it's False.
51
52
  Otherwise (``force_level is None``) use the value of ``prerelease``
52
53
  """
53
- log.debug(", ".join(f"{k} = {v}" for k, v in locals().items()))
54
- return force_prerelease or ((force_level is None) and prerelease)
54
+ local_vars = list(locals().items())
55
+ log.debug(
56
+ "%s: %s",
57
+ is_forced_prerelease.__name__,
58
+ ", ".join(f"{k} = {v}" for k, v in local_vars),
59
+ )
60
+ return (
61
+ as_prerelease
62
+ or forced_level_bump is LevelBump.PRERELEASE_REVISION
63
+ or ((forced_level_bump is None) and prerelease)
64
+ )
55
65
 
56
66
 
57
67
  def last_released(
@@ -62,16 +72,41 @@ def last_released(
62
72
 
63
73
 
64
74
  def version_from_forced_level(
65
- repo: Repo, level_bump: LevelBump, translator: VersionTranslator
75
+ repo: Repo, forced_level_bump: LevelBump, translator: VersionTranslator
66
76
  ) -> Version:
67
77
  ts_and_vs = tags_and_versions(repo.tags, translator)
68
78
 
69
79
  # If we have no tags, return the default version
70
80
  if not ts_and_vs:
71
- return Version.parse(DEFAULT_VERSION).bump(level_bump)
81
+ return Version.parse(DEFAULT_VERSION).bump(forced_level_bump)
72
82
 
73
83
  _, latest_version = ts_and_vs[0]
74
- return latest_version.bump(level_bump)
84
+ if forced_level_bump is not LevelBump.PRERELEASE_REVISION:
85
+ return latest_version.bump(forced_level_bump)
86
+
87
+ # We need to find the latest version with the prerelease token
88
+ # we're looking for, and return that version + an increment to
89
+ # the prerelease revision.
90
+
91
+ # NOTE this can probably be cleaned up.
92
+ # ts_and_vs are in order, so check if we're looking at prereleases
93
+ # for the same (major, minor, patch) as the latest version.
94
+ # If we are, we can increment the revision and we're done. If
95
+ # we don't find a prerelease targeting this version with the same
96
+ # token as the one we're looking to prerelease, we can use revision 1.
97
+ for _, version in ts_and_vs:
98
+ if not (
99
+ version.major == latest_version.major
100
+ and version.minor == latest_version.minor
101
+ and version.patch == latest_version.patch
102
+ ):
103
+ break
104
+ if (
105
+ version.is_prerelease
106
+ and version.prerelease_token == translator.prerelease_token
107
+ ):
108
+ return version.bump(LevelBump.PRERELEASE_REVISION)
109
+ return latest_version.to_prerelease(token=translator.prerelease_token, revision=1)
75
110
 
76
111
 
77
112
  def apply_version_to_source_files(
@@ -100,7 +135,9 @@ def apply_version_to_source_files(
100
135
  return paths
101
136
 
102
137
 
103
- def shell(cmd: str, *, check: bool = True) -> subprocess.CompletedProcess:
138
+ def shell(
139
+ cmd: str, *, env: Mapping[str, str] | None = None, check: bool = True
140
+ ) -> subprocess.CompletedProcess:
104
141
  shell: str | None
105
142
  try:
106
143
  shell, _ = shellingham.detect_shell()
@@ -114,6 +151,7 @@ def shell(cmd: str, *, check: bool = True) -> subprocess.CompletedProcess:
114
151
 
115
152
  return subprocess.run(
116
153
  [shell, "-c" if shell != "cmd" else "/c", cmd], # noqa: S603
154
+ env=(env or {}),
117
155
  check=check,
118
156
  )
119
157
 
@@ -145,10 +183,10 @@ def shell(cmd: str, *, check: bool = True) -> subprocess.CompletedProcess:
145
183
  help="Print the last released version tag and exit",
146
184
  )
147
185
  @click.option(
148
- "--prerelease",
149
- "force_prerelease",
186
+ "--as-prerelease",
187
+ "as_prerelease",
150
188
  is_flag=True,
151
- help="Force the next version to be a prerelease",
189
+ help="Ensure the next version to be released is a prerelease version",
152
190
  )
153
191
  @click.option(
154
192
  "--prerelease-token",
@@ -160,19 +198,25 @@ def shell(cmd: str, *, check: bool = True) -> subprocess.CompletedProcess:
160
198
  "--major",
161
199
  "force_level",
162
200
  flag_value="major",
163
- help="force the next version to be a major release",
201
+ help="Force the next version to be a major release",
164
202
  )
165
203
  @click.option(
166
204
  "--minor",
167
205
  "force_level",
168
206
  flag_value="minor",
169
- help="force the next version to be a minor release",
207
+ help="Force the next version to be a minor release",
170
208
  )
171
209
  @click.option(
172
210
  "--patch",
173
211
  "force_level",
174
212
  flag_value="patch",
175
- help="force the next version to be a patch release",
213
+ help="Force the next version to be a patch release",
214
+ )
215
+ @click.option(
216
+ "--prerelease",
217
+ "force_level",
218
+ flag_value="prerelease_revision",
219
+ help="Force the next version to be a prerelease",
176
220
  )
177
221
  @click.option(
178
222
  "--commit/--no-commit",
@@ -224,7 +268,7 @@ def version( # noqa: C901
224
268
  print_only_tag: bool = False,
225
269
  print_last_released: bool = False,
226
270
  print_last_released_tag: bool = False,
227
- force_prerelease: bool = False,
271
+ as_prerelease: bool = False,
228
272
  prerelease_token: str | None = None,
229
273
  force_level: str | None = None,
230
274
  commit_changes: bool = True,
@@ -271,9 +315,10 @@ def version( # noqa: C901
271
315
  ctx.exit(0)
272
316
 
273
317
  parser = runtime.commit_parser
318
+ forced_level_bump = None if not force_level else LevelBump.from_string(force_level)
274
319
  prerelease = is_forced_prerelease(
275
- force_prerelease=force_prerelease,
276
- force_level=force_level,
320
+ as_prerelease=as_prerelease,
321
+ forced_level_bump=forced_level_bump,
277
322
  prerelease=runtime.prerelease,
278
323
  )
279
324
  hvcs_client = runtime.hvcs_client
@@ -306,23 +351,19 @@ def version( # noqa: C901
306
351
  log.info("No vcs release will be created because pushing changes is disabled")
307
352
  make_vcs_release &= push_changes
308
353
 
309
- if force_prerelease:
310
- log.warning("Forcing prerelease due to '--prerelease' command-line flag")
311
- elif force_level:
354
+ if forced_level_bump:
312
355
  log.warning(
313
- "Forcing prerelease=False due to '--%s' command-line flag and no "
314
- "'--prerelease' flag",
356
+ "Forcing a '%s' release due to '--%s' command-line flag",
315
357
  force_level,
316
- )
317
-
318
- if force_level:
319
- level_bump = LevelBump.from_string(force_level)
320
- log.warning(
321
- "Forcing a %s level bump due to '--force' command-line option", force_level
358
+ (
359
+ force_level
360
+ if forced_level_bump is not LevelBump.PRERELEASE_REVISION
361
+ else "prerelease"
362
+ ),
322
363
  )
323
364
 
324
365
  new_version = version_from_forced_level(
325
- repo=repo, level_bump=level_bump, translator=translator
366
+ repo=repo, forced_level_bump=forced_level_bump, translator=translator
326
367
  )
327
368
 
328
369
  # We only turn the forced version into a prerelease if the user has specified
@@ -347,6 +388,17 @@ def version( # noqa: C901
347
388
  if build_metadata:
348
389
  new_version.build_metadata = build_metadata
349
390
 
391
+ if as_prerelease:
392
+ before_conversion, new_version = (
393
+ new_version,
394
+ new_version.to_prerelease(token=translator.prerelease_token),
395
+ )
396
+ log.info(
397
+ "Converting %s to %s due to '--as-prerelease' command-line option",
398
+ before_conversion,
399
+ new_version,
400
+ )
401
+
350
402
  gha_output.released = False
351
403
  gha_output.version = new_version
352
404
  ctx.call_on_close(gha_output.write_if_possible)
@@ -458,7 +510,35 @@ def version( # noqa: C901
458
510
  "[bold green]:hammer_and_wrench: Running build command: "
459
511
  + build_command
460
512
  )
461
- shell(build_command, check=True)
513
+ shell(
514
+ build_command,
515
+ check=True,
516
+ env=dict(
517
+ filter(
518
+ lambda k_v: k_v[1] is not None, # type: ignore
519
+ {
520
+ "NEW_VERSION": str(new_version),
521
+ "PATH": os.getenv("PATH", ""),
522
+ "HOME": os.getenv("HOME", None),
523
+ "VIRTUAL_ENV": os.getenv("VIRTUAL_ENV", None),
524
+ # affects build decisions
525
+ "CI": os.getenv("CI", None),
526
+ # Identifies which CI environment
527
+ "GITHUB_ACTIONS": os.getenv("GITHUB_ACTIONS", None),
528
+ "GITLAB_CI": os.getenv("GITLAB_CI", None),
529
+ "GITEA_ACTIONS": os.getenv("GITEA_ACTIONS", None),
530
+ "BITBUCKET_CI": (
531
+ str(True).lower()
532
+ if os.getenv("BITBUCKET_REPO_FULL_NAME", None)
533
+ else None
534
+ ),
535
+ "PSR_DOCKER_GITHUB_ACTION": os.getenv(
536
+ "PSR_DOCKER_GITHUB_ACTION", None
537
+ ),
538
+ }.items(),
539
+ )
540
+ ),
541
+ )
462
542
  except subprocess.CalledProcessError as exc:
463
543
  ctx.fail(str(exc))
464
544
 
@@ -601,7 +681,7 @@ def version( # noqa: C901
601
681
 
602
682
  gha_output.released = True
603
683
 
604
- if make_vcs_release:
684
+ if make_vcs_release and isinstance(hvcs_client, RemoteHvcsBase):
605
685
  if opts.noop:
606
686
  noop_report(
607
687
  f"would have created a release for the tag {new_version.as_tag()!r}"
@@ -630,10 +710,11 @@ def version( # noqa: C901
630
710
  noop_report(f"would have uploaded the following assets: {runtime.assets}")
631
711
  else:
632
712
  try:
633
- release_id = hvcs_client.create_or_update_release(
713
+ hvcs_client.create_release(
634
714
  tag=new_version.as_tag(),
635
715
  release_notes=release_notes,
636
716
  prerelease=new_version.is_prerelease,
717
+ assets=assets,
637
718
  )
638
719
  except HTTPError as err:
639
720
  log.exception(err)
@@ -654,15 +735,4 @@ def version( # noqa: C901
654
735
  log.exception(e)
655
736
  ctx.fail(str(e))
656
737
 
657
- for asset in assets:
658
- log.info("Uploading asset %s", asset)
659
- try:
660
- hvcs_client.upload_asset(release_id, asset)
661
- except HTTPError as err:
662
- log.exception(err)
663
- ctx.fail(str.join("\n", [str(err), "Failed to upload asset!"]))
664
- except Exception as e:
665
- log.exception(e)
666
- ctx.fail(str(e))
667
-
668
738
  return str(new_version)
@@ -39,8 +39,13 @@ from semantic_release.commit_parser import (
39
39
  TagCommitParser,
40
40
  )
41
41
  from semantic_release.const import COMMIT_MESSAGE, DEFAULT_COMMIT_AUTHOR, SEMVER_REGEX
42
- from semantic_release.errors import InvalidConfiguration, NotAReleaseBranch
42
+ from semantic_release.errors import (
43
+ InvalidConfiguration,
44
+ NotAReleaseBranch,
45
+ ParserLoadError,
46
+ )
43
47
  from semantic_release.helpers import dynamic_import
48
+ from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase
44
49
  from semantic_release.version import VersionTranslator
45
50
  from semantic_release.version.declaration import (
46
51
  PatternVersionDeclaration,
@@ -141,13 +146,22 @@ class RemoteConfig(BaseModel):
141
146
  def set_default_token(self) -> Self:
142
147
  # Set the default token name for the given VCS when no user input is given
143
148
  if not self.token and self.type in _known_hvcs:
144
- default_token_name = _known_hvcs[self.type].DEFAULT_ENV_TOKEN_NAME
145
- if default_token_name:
146
- env_token = EnvConfigVar(env=default_token_name).getvalue()
147
- if env_token:
148
- self.token = env_token
149
+ if env_token := self._get_default_token():
150
+ self.token = env_token
149
151
  return self
150
152
 
153
+ def _get_default_token(self) -> str | None:
154
+ hvcs_client_class = _known_hvcs[self.type]
155
+ default_hvcs_instance = hvcs_client_class("git@example.com:owner/project.git")
156
+ if not isinstance(default_hvcs_instance, RemoteHvcsBase):
157
+ return None
158
+
159
+ default_token_name = default_hvcs_instance.DEFAULT_ENV_TOKEN_NAME
160
+ if not default_token_name:
161
+ return None
162
+
163
+ return EnvConfigVar(env=default_token_name).getvalue()
164
+
151
165
  @model_validator(mode="after")
152
166
  def check_url_scheme(self) -> Self:
153
167
  if self.url and isinstance(self.url, str):
@@ -222,18 +236,50 @@ class RawConfig(BaseModel):
222
236
  parser_opts_type = None
223
237
  # If the commit parser is a known one, pull the default options object from it
224
238
  if self.commit_parser in _known_commit_parsers:
239
+ # TODO: BREAKING CHANGE v10
240
+ # parser_opts_type = (
241
+ # _known_commit_parsers[self.commit_parser]
242
+ # .get_default_options()
243
+ # .__class__
244
+ # )
225
245
  parser_opts_type = _known_commit_parsers[
226
246
  self.commit_parser
227
247
  ].parser_options
228
248
  else:
229
- # if its a custom parser, try to import it and pull the default options object type
230
- custom_class = dynamic_import(self.commit_parser)
231
- if hasattr(custom_class, "parser_options"):
232
- parser_opts_type = custom_class.parser_options
249
+ try:
250
+ # if its a custom parser, try to import it and pull the default options object type
251
+ custom_class = dynamic_import(self.commit_parser)
252
+ # TODO: BREAKING CHANGE v10
253
+ # parser_opts_type = custom_class.get_default_options().__class__
254
+ if hasattr(custom_class, "parser_options"):
255
+ parser_opts_type = custom_class.parser_options
256
+
257
+ except ModuleNotFoundError as err:
258
+ raise ParserLoadError(
259
+ str.join(
260
+ "\n",
261
+ [
262
+ str(err),
263
+ "Unable to import your custom parser! Check your configuration!",
264
+ ],
265
+ )
266
+ ) from err
267
+
268
+ except AttributeError as err:
269
+ raise ParserLoadError(
270
+ str.join(
271
+ "\n",
272
+ [
273
+ str(err),
274
+ "Unable to find your custom parser class inside the given module.",
275
+ "Check your configuration!",
276
+ ],
277
+ )
278
+ ) from err
233
279
 
234
280
  # from either the custom opts class or the known parser opts class, create an instance
235
281
  if callable(parser_opts_type):
236
- opts_obj = parser_opts_type()
282
+ opts_obj: Any = parser_opts_type()
237
283
  # if the opts object is a dataclass, wrap it in a RootModel so it can be transformed to a Mapping
238
284
  opts_obj = (
239
285
  opts_obj if not is_dataclass(opts_obj) else RootModel(opts_obj)
@@ -373,15 +419,44 @@ class RuntimeContext:
373
419
  branch_config = cls.select_branch_options(raw.branches, active_branch)
374
420
 
375
421
  # commit_parser
376
- commit_parser_cls = (
377
- _known_commit_parsers[raw.commit_parser]
378
- if raw.commit_parser in _known_commit_parsers
379
- else dynamic_import(raw.commit_parser)
380
- )
422
+ try:
423
+ commit_parser_cls = (
424
+ _known_commit_parsers[raw.commit_parser]
425
+ if raw.commit_parser in _known_commit_parsers
426
+ else dynamic_import(raw.commit_parser)
427
+ )
428
+ except ModuleNotFoundError as err:
429
+ raise ParserLoadError(
430
+ str.join(
431
+ "\n",
432
+ [
433
+ str(err),
434
+ "Unable to import your custom parser! Check your configuration!",
435
+ ],
436
+ )
437
+ ) from err
438
+ except AttributeError as err:
439
+ raise ParserLoadError(
440
+ str.join(
441
+ "\n",
442
+ [
443
+ str(err),
444
+ "Unable to find the parser class inside the given module",
445
+ ],
446
+ )
447
+ ) from err
381
448
 
382
- commit_parser = commit_parser_cls(
383
- options=commit_parser_cls.parser_options(**raw.commit_parser_options)
384
- )
449
+ commit_parser_opts_class = commit_parser_cls.parser_options
450
+ # TODO: Breaking change v10
451
+ # commit_parser_opts_class = commit_parser_cls.get_default_options().__class__
452
+ try:
453
+ commit_parser = commit_parser_cls(
454
+ options=commit_parser_opts_class(**raw.commit_parser_options)
455
+ )
456
+ except TypeError as err:
457
+ raise ParserLoadError(
458
+ str.join("\n", [str(err), f"Failed to initialize {raw.commit_parser}"])
459
+ ) from err
385
460
 
386
461
  # We always exclude PSR's own release commits from the Changelog
387
462
  # when parsing commits
@@ -65,10 +65,19 @@ class CommitParser(ABC, Generic[_TT, _OPTS]):
65
65
  ...
66
66
  """
67
67
 
68
- parser_options: type[_OPTS]
69
-
70
- def __init__(self, options: _OPTS) -> None:
71
- self.options = options
68
+ # TODO: Deprecate in lieu of get_default_options()
69
+ parser_options: type[ParserOptions] = ParserOptions
70
+
71
+ def __init__(self, options: _OPTS | None = None) -> None:
72
+ self.options: _OPTS = (
73
+ options if options is not None else self.get_default_options()
74
+ )
75
+
76
+ # TODO: BREAKING CHANGE v10, add abstract method for all custom parsers
77
+ # @staticmethod
78
+ # @abstractmethod
79
+ def get_default_options(self) -> _OPTS:
80
+ return self.parser_options() # type: ignore
72
81
 
73
82
  @abstractmethod
74
83
  def parse(self, commit: Commit) -> _TT: ...