From 4a148cecf59d39a5e511e0c8d05f5dbb9ad0a09c Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 20:56:07 -0600 Subject: [PATCH 01/13] ci(deps): bump `python-semantic-release@v10.0.1` action to `v10.0.2` --- .github/workflows/cicd.yml | 2 +- .github/workflows/validate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index f63564b7c..2209b8bbb 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -145,7 +145,7 @@ jobs: - name: Release | Python Semantic Release id: release - uses: ./ + uses: python-semantic-release/python-semantic-release@1a324000f2251a9e722e77b128bf72712653813f # v10.0.2 with: github_token: ${{ secrets.GITHUB_TOKEN }} verbosity: 1 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 89535f255..f20aa9700 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -112,7 +112,7 @@ jobs: - name: Build | Build next version artifacts id: version - uses: python-semantic-release/python-semantic-release@917a2c730cb8f6c8cd3d00f23c876d724a4a844c # v10.0.1 + uses: python-semantic-release/python-semantic-release@1a324000f2251a9e722e77b128bf72712653813f # v10.0.2 with: github_token: "" verbosity: 1 From 55486cd9551dafe57b715c06473fdc964f74e5e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 02:54:34 +0000 Subject: [PATCH 02/13] ci(deps): bump `python-semantic-release/publish-action@v10.0.1` to `v10.0.2` --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 2209b8bbb..512454ab0 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -152,7 +152,7 @@ jobs: build: false - name: Release | Add distribution artifacts to GitHub Release Assets - uses: python-semantic-release/publish-action@d3a9934c4fff57f0d4df24450566d3dba7e7082a # v10.0.1 + uses: python-semantic-release/publish-action@e5e3010f6a207cd5d6f5d3dccedbea355484ca02 # v10.0.2 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} From b1151b1d3a3e8c3ff82435d4302f481d0c99b7a1 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 26 May 2025 13:39:52 -0600 Subject: [PATCH 03/13] refactor(changelog): simplify & consolidate jinja macro functionality (#1264) * refactor(changelog-md): simplify & consolidate jinja macro functionality * refactor(changelog-rst): simplify & consolidate jinja macro functionality --- .../conventional/md/.components/macros.md.j2 | 189 ++++++-------- .../rst/.components/macros.rst.j2 | 235 ++++++++---------- 2 files changed, 178 insertions(+), 246 deletions(-) diff --git a/src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 index 8bb56ea79..13cc18fac 100644 --- a/src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 +++ b/src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 @@ -6,41 +6,50 @@ %} +{# + MACRO: Capitalize the first letter of a string only +#}{% macro capitalize_first_letter_only(sentence) +%}{{ (sentence[0] | upper) ~ sentence[1:] +}}{% endmacro +%} + + {# MACRO: commit message links or PR/MR links of commit #}{% macro commit_msg_links(commit) %}{% if commit.error is undefined -%}{% set commit_hash_link = format_link( - commit.hexsha | commit_hash_url, - "`%s`" | format(commit.short_hash) - ) %}{# -#}{% set summary_line = commit.descriptions[0] | safe -%}{% set summary_line = [ - summary_line.split(" ", maxsplit=1)[0] | capitalize, - summary_line.split(" ", maxsplit=1)[1] - ] | join(" ") + # # Initialize variables +#}{% set link_references = [] +%}{% set summary_line = capitalize_first_letter_only( + commit.descriptions[0] | safe + ) %}{# #}{% if commit.linked_merge_request != "" %}{# # Add PR references with a link to the PR -#}{% set pr_num = commit.linked_merge_request -%}{% set pr_link = format_link(pr_num | pull_request_url, pr_num) -%}{# - # TODO: breaking change v10, remove summary line replacers as PSR will do it for us -#}{% set summary_line = summary_line | replace("(pull request", "(") | replace("(" ~ pr_num ~ ")", "") | trim -%}{% set summary_line = "%s (%s, %s)" | format( - summary_line, - pr_link, - commit_hash_link, +#}{% set _ = link_references.append( + format_link( + commit.linked_merge_request | pull_request_url, + commit.linked_merge_request + ) ) +%}{% endif +%}{# + # # DEFAULT: Always include the commit hash as a link +#}{% set _ = link_references.append( + format_link( + commit.hexsha | commit_hash_url, + "`%s`" | format(commit.short_hash) + ) + ) %}{# - # DEFAULT: No PR identifier found, so just append commit hash as url to the commit summary_line -#}{% else -%}{% set summary_line = "%s (%s)" | format(summary_line, commit_hash_link) +#}{% set formatted_links = "" +%}{% if link_references | length > 0 +%}{% set formatted_links = " (%s)" | format(link_references | join(", ")) %}{% endif %}{# # Return the modified summary_line -#}{{ summary_line +#}{{ summary_line ~ formatted_links }}{% endif %}{% endmacro %} @@ -71,24 +80,21 @@ {# - MACRO: format the breaking changes description by: - - Capitalizing the description + MACRO: format a commit descriptions list by: + - Capitalizing the first line of the description - Adding an optional scope prefix -#}{% macro format_breaking_changes_description(commit) -%}{% set ns = namespace(full_description="") + - Joining the rest of the descriptions with a double newline +#}{% macro format_attr_paragraphs(commit, attribute) +%}{# NOTE: requires namespace because of the way Jinja2 handles variable scoping with loops +#}{% set ns = namespace(full_description="") %}{# #}{% if commit.error is undefined -%}{% for paragraph in commit.breaking_descriptions +%}{% for paragraph in commit | attr(attribute) %}{% if paragraph | trim | length > 0 %}{# -#}{% set paragraph_text = [ - paragraph.split(" ", maxsplit=1)[0] | capitalize, - paragraph.split(" ", maxsplit=1)[1] - ] | join(" ") | trim | safe -%}{# #}{% set ns.full_description = [ ns.full_description, - paragraph_text + capitalize_first_letter_only(paragraph) | trim | safe, ] | join("\n\n") %}{# #}{% endif @@ -108,65 +114,48 @@ %} +{# + MACRO: format the breaking changes description by: + - Capitalizing the description + - Adding an optional scope prefix +#}{% macro format_breaking_changes_description(commit) +%}{{ format_attr_paragraphs(commit, 'breaking_descriptions') +}}{% endmacro +%} + + {# MACRO: format the release notice by: - Capitalizing the description - Adding an optional scope prefix #}{% macro format_release_notice(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.release_notices -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = [ - paragraph.split(" ", maxsplit=1)[0] | capitalize, - paragraph.split(" ", maxsplit=1)[1] - ] | join(" ") | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim -%}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) -%}{% endif -%}{% endif -%}{# -#}{{ ns.full_description +%}{{ format_attr_paragraphs(commit, "release_notices") }}{% endmacro %} {# - MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_descriptions(ns) + MACRO: order commits alphabetically by scope and attribute + - Commits are sorted based on scope and then the attribute alphabetically + - Commits without scope are placed first and sorted alphabetically by the attribute + - parameter: ns (namespace) object with a commits list + - parameter: attr (string) attribute to sort by + - returns None but modifies the ns.commits list in place +#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) %}{% set ordered_commits = [] %}{# # # Eliminate any ParseError commits from input set #}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list %}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor + # # grab all commits with no scope and sort alphabetically by attr +#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor %}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor + # # grab all commits with a scope and sort alphabetically by the scope and then attr +#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor %}{# # # Return the ordered commits #}{% set ns.commits = ordered_commits @@ -174,6 +163,18 @@ %} +{# + MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_descriptions(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') +%}{% endmacro +%} + + {# MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes - Commits are sorted based on the commit type and the commit message @@ -181,23 +182,7 @@ - parameter: ns (namespace) object with a commits list - returns None but modifies the ns.commits list in place #}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='breaking_descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,breaking_descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') %}{% endmacro %} @@ -209,22 +194,6 @@ - parameter: ns (namespace) object with a commits list - returns None but modifies the ns.commits list in place #}{% macro apply_alphabetical_ordering_by_release_notices(ns) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='release_notices.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,release_notices.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') %}{% endmacro %} diff --git a/src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 index f70043036..6fc7f90ff 100644 --- a/src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 +++ b/src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 @@ -1,3 +1,11 @@ +{# + MACRO: Capitalize the first letter of a string only +#}{% macro capitalize_first_letter_only(sentence) +%}{{ (sentence[0] | upper) ~ sentence[1:] +}}{% endmacro +%} + + {# MACRO: format a post-paragraph link reference in RST #}{% macro format_link_reference(link, label) @@ -6,6 +14,49 @@ %} +{# MACRO: generate a heading underline that matches the exact length of the header #} +{% macro generate_heading_underline(header, underline_char) +%}{% set header_underline = [] +%}{% for _ in header +%}{% set __ = header_underline.append(underline_char) +%}{% endfor +%}{# # Print out the header underline +#}{{ header_underline | join +}}{% endmacro +%} + + +{# + MACRO: formats a commit message for a non-inline RST link for a commit hash and/or PR/MR +#}{% macro commit_msg_links(commit) +%}{% if commit.error is undefined +%}{# + # # Initialize variables +#}{% set link_references = [] +%}{% set summary_line = capitalize_first_letter_only( + commit.descriptions[0] | safe + ) +%}{# +#}{% if commit.linked_merge_request != "" +%}{# # Add PR/MR references with a link to the PR/MR +#}{% set _ = link_references.append("`%s`_" | format(commit.linked_merge_request)) +%}{% endif +%}{# + # DEFAULT: Always include the commit hash as a link +#}{% set _ = link_references.append("`%s`_" | format(commit.short_hash)) +%}{# +#}{% set formatted_links = "" +%}{% if link_references | length > 0 +%}{% set formatted_links = " (%s)" | format(link_references | join(", ")) +%}{% endif +%}{# + # Return the modified summary_line +#}{{ summary_line ~ formatted_links +}}{% endif +%}{% endmacro +%} + + {# MACRO: format commit summary line #}{% macro format_commit_summary_line(commit) @@ -50,72 +101,21 @@ {# - MACRO: formats a commit message for a non-inline RST link for a commit hash and/or PR/MR -#}{% macro commit_msg_links(commit, hvcs_type) -%}{% if commit.error is undefined -%}{% set commit_hash_link = "`%s`_" | format(commit.short_hash) -%}{# -#}{% set summary_line = commit.descriptions[0] | safe -%}{% set summary_line = [ - summary_line.split(" ", maxsplit=1)[0] | capitalize, - summary_line.split(" ", maxsplit=1)[1] - ] | join(" ") -%}{# -#}{% if commit.linked_merge_request != "" -%}{# # Add PR references with a link to the PR -#}{% set pr_link = "`%s`_" | format(commit.linked_merge_request) -%}{# - # TODO: breaking change v10, remove summary line replacers as PSR will do it for us -#}{% set summary_line = summary_line | replace("(pull request ", "(") | replace("(" ~ commit.linked_merge_request ~ ")", "") | trim -%}{% set summary_line = "%s (%s, %s)" | format( - summary_line, - pr_link, - commit_hash_link, - ) -%}{# - # DEFAULT: No PR identifier found, so just append a commit hash as url to the commit summary_line -#}{% else -%}{% set summary_line = "%s (%s)" | format(summary_line, commit_hash_link) -%}{% endif -%}{# - # Return the modified summary_line -#}{{ summary_line -}}{% endif -%}{% endmacro -%} - - -{# MACRO: generate a heading underline that matches the exact length of the header #} -{% macro generate_heading_underline(header, underline_char) -%}{% set header_underline = [] -%}{% for _ in header -%}{{ header_underline.append(underline_char) | default("", true) -}}{% endfor -%}{# # Print out the header underline -#}{{ header_underline | join -}}{% endmacro -%} - - -{# - MACRO: format the breaking changes description by: - - Capitalizing the description + MACRO: format a commit descriptions list by: + - Capitalizing the first line of the description - Adding an optional scope prefix -#}{% macro format_breaking_changes_description(commit) -%}{% set ns = namespace(full_description="") + - Joining the rest of the descriptions with a double newline +#}{% macro format_attr_paragraphs(commit, attribute) +%}{# NOTE: requires namespace because of the way Jinja2 handles variable scoping with loops +#}{% set ns = namespace(full_description="") %}{# #}{% if commit.error is undefined -%}{% for paragraph in commit.breaking_descriptions +%}{% for paragraph in commit | attr(attribute) %}{% if paragraph | trim | length > 0 %}{# -#}{% set paragraph_text = [ - paragraph.split(" ", maxsplit=1)[0] | capitalize, - paragraph.split(" ", maxsplit=1)[1] - ] | join(" ") | trim | safe -%}{# #}{% set ns.full_description = [ ns.full_description, - paragraph_text + capitalize_first_letter_only(paragraph) | trim | safe, ] | join("\n\n") %}{# #}{% endif @@ -135,65 +135,48 @@ %} +{# + MACRO: format the breaking changes description by: + - Capitalizing the description + - Adding an optional scope prefix +#}{% macro format_breaking_changes_description(commit) +%}{{ format_attr_paragraphs(commit, 'breaking_descriptions') +}}{% endmacro +%} + + {# MACRO: format the release notice by: - Capitalizing the description - Adding an optional scope prefix #}{% macro format_release_notice(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.release_notices -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = [ - paragraph.split(" ", maxsplit=1)[0] | capitalize, - paragraph.split(" ", maxsplit=1)[1] - ] | join(" ") | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim -%}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) -%}{% endif -%}{% endif -%}{# -#}{{ ns.full_description +%}{{ format_attr_paragraphs(commit, "release_notices") }}{% endmacro %} {# - MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_descriptions(ns) + MACRO: order commits alphabetically by scope and attribute + - Commits are sorted based on scope and then the attribute alphabetically + - Commits without scope are placed first and sorted alphabetically by the attribute + - parameter: ns (namespace) object with a commits list + - parameter: attr (string) attribute to sort by + - returns None but modifies the ns.commits list in place +#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) %}{% set ordered_commits = [] %}{# # # Eliminate any ParseError commits from input set #}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list %}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor + # # grab all commits with no scope and sort alphabetically by attr +#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor %}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor + # # grab all commits with a scope and sort alphabetically by the scope and then attr +#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor %}{# # # Return the ordered commits #}{% set ns.commits = ordered_commits @@ -201,6 +184,18 @@ %} +{# + MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_descriptions(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') +%}{% endmacro +%} + + {# MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes - Commits are sorted based on the commit type and the commit message @@ -208,23 +203,7 @@ - parameter: ns (namespace) object with a commits list - returns None but modifies the ns.commits list in place #}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='breaking_descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,breaking_descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') %}{% endmacro %} @@ -236,22 +215,6 @@ - parameter: ns (namespace) object with a commits list - returns None but modifies the ns.commits list in place #}{% macro apply_alphabetical_ordering_by_release_notices(ns) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='release_notices.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,release_notices.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') %}{% endmacro %} From 10e1b52f4b3f5ccdfa322c3005dec3381b97fa81 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 26 May 2025 13:43:11 -0600 Subject: [PATCH 04/13] chore(psr-changelog): consolidate & simplify jinja macro implementation (#1265) --- .../.components/changes.md.j2 | 13 +- .../.components/changes.rst.j2 | 17 +- .../.components/macros.common.j2 | 160 ++++++++++ .../.components/macros.md.j2 | 174 +---------- .../.components/macros.rst.j2 | 275 ++++-------------- 5 files changed, 229 insertions(+), 410 deletions(-) create mode 100644 config/release-templates/.components/macros.common.j2 diff --git a/config/release-templates/.components/changes.md.j2 b/config/release-templates/.components/changes.md.j2 index d2a062d9a..6cdef2d17 100644 --- a/config/release-templates/.components/changes.md.j2 +++ b/config/release-templates/.components/changes.md.j2 @@ -1,9 +1,10 @@ -{% from 'macros.md.j2' import apply_alphabetical_ordering_by_brk_descriptions -%}{% from 'macros.md.j2' import apply_alphabetical_ordering_by_descriptions -%}{% from 'macros.md.j2' import apply_alphabetical_ordering_by_release_notices -%}{% from 'macros.md.j2' import emoji_map, format_breaking_changes_description -%}{% from 'macros.md.j2' import format_commit_summary_line, format_release_notice -%}{% from 'macros.md.j2' import section_heading_order, section_heading_translations +{% from 'macros.common.j2' import apply_alphabetical_ordering_by_brk_descriptions +%}{% from 'macros.common.j2' import apply_alphabetical_ordering_by_descriptions +%}{% from 'macros.common.j2' import apply_alphabetical_ordering_by_release_notices +%}{% from 'macros.common.j2' import emoji_map, format_breaking_changes_description +%}{% from 'macros.common.j2' import format_release_notice, section_heading_order +%}{% from 'macros.common.j2' import section_heading_translations +%}{% from 'macros.md.j2' import format_commit_summary_line %}{# EXAMPLE: diff --git a/config/release-templates/.components/changes.rst.j2 b/config/release-templates/.components/changes.rst.j2 index 90434bfdb..9751108c2 100644 --- a/config/release-templates/.components/changes.rst.j2 +++ b/config/release-templates/.components/changes.rst.j2 @@ -1,11 +1,12 @@ -{% from 'macros.rst.j2' import apply_alphabetical_ordering_by_brk_descriptions -%}{% from 'macros.rst.j2' import apply_alphabetical_ordering_by_descriptions -%}{% from 'macros.rst.j2' import apply_alphabetical_ordering_by_release_notices -%}{% from 'macros.rst.j2' import emoji_map, extract_issue_link_references, extract_pr_link_reference -%}{% from 'macros.rst.j2' import format_breaking_changes_description, format_commit_summary_line -%}{% from 'macros.rst.j2' import format_link_reference, format_release_notice -%}{% from 'macros.rst.j2' import generate_heading_underline, section_heading_order -%}{% from 'macros.rst.j2' import section_heading_translations +{% from 'macros.common.j2' import apply_alphabetical_ordering_by_brk_descriptions +%}{% from 'macros.common.j2' import apply_alphabetical_ordering_by_descriptions +%}{% from 'macros.common.j2' import apply_alphabetical_ordering_by_release_notices +%}{% from 'macros.common.j2' import emoji_map, format_breaking_changes_description +%}{% from 'macros.common.j2' import format_release_notice, section_heading_order +%}{% from 'macros.common.j2' import section_heading_translations +%}{% from 'macros.rst.j2' import extract_issue_link_references, extract_pr_link_reference +%}{% from 'macros.rst.j2' import format_commit_summary_line, format_link_reference +%}{% from 'macros.rst.j2' import generate_heading_underline %}{# โœจ Features diff --git a/config/release-templates/.components/macros.common.j2 b/config/release-templates/.components/macros.common.j2 new file mode 100644 index 000000000..5ec7ff6d0 --- /dev/null +++ b/config/release-templates/.components/macros.common.j2 @@ -0,0 +1,160 @@ +{# TODO: move to configuration for user to modify #} +{% set section_heading_translations = { + 'feat': 'features', + 'fix': 'bug fixes', + 'perf': 'performance improvements', + 'docs': 'documentation', + 'build': 'build system', + 'refactor': 'refactoring', + 'test': 'testing', + 'ci': 'continuous integration', + 'chore': 'chores', + 'style': 'code style', + } +%} + +{% set section_heading_order = section_heading_translations.values() %} + +{% set emoji_map = { + 'breaking': '๐Ÿ’ฅ', + 'features': 'โœจ', + 'bug fixes': '๐Ÿชฒ', + 'performance improvements': 'โšก', + 'documentation': '๐Ÿ“–', + 'build system': 'โš™๏ธ', + 'refactoring': 'โ™ป๏ธ', + 'testing': 'โœ…', + 'continuous integration': '๐Ÿค–', + 'chores': '๐Ÿงน', + 'code style': '๐ŸŽจ', + 'unknown': 'โ—', + 'release_note': '๐Ÿ’ก', +} %} + + +{# + MACRO: Capitalize the first letter of a string only +#}{% macro capitalize_first_letter_only(sentence) +%}{{ (sentence[0] | upper) ~ sentence[1:] +}}{% endmacro +%} + + +{# + MACRO: format a commit descriptions list by: + - Capitalizing the first line of the description + - Adding an optional scope prefix + - Joining the rest of the descriptions with a double newline +#}{% macro format_attr_paragraphs(commit, attribute) +%}{# NOTE: requires namespace because of the way Jinja2 handles variable scoping with loops +#}{% set ns = namespace(full_description="") +%}{# +#}{% if commit.error is undefined +%}{% for paragraph in commit | attr(attribute) +%}{% if paragraph | trim | length > 0 +%}{# +#}{% set ns.full_description = [ + ns.full_description, + capitalize_first_letter_only(paragraph) | trim | safe, + ] | join("\n\n") +%}{# +#}{% endif +%}{% endfor +%}{# +#}{% set ns.full_description = ns.full_description | trim +%}{# +#}{% if commit.scope +%}{% set ns.full_description = "**%s**: %s" | format( + commit.scope, ns.full_description + ) +%}{% endif +%}{% endif +%}{# +#}{{ ns.full_description +}}{% endmacro +%} + + +{# + MACRO: format the breaking changes description by: + - Capitalizing the description + - Adding an optional scope prefix +#}{% macro format_breaking_changes_description(commit) +%}{{ format_attr_paragraphs(commit, 'breaking_descriptions') +}}{% endmacro +%} + + +{# + MACRO: format the release notice by: + - Capitalizing the description + - Adding an optional scope prefix +#}{% macro format_release_notice(commit) +%}{{ format_attr_paragraphs(commit, "release_notices") +}}{% endmacro +%} + + +{# + MACRO: order commits alphabetically by scope and attribute + - Commits are sorted based on scope and then the attribute alphabetically + - Commits without scope are placed first and sorted alphabetically by the attribute + - parameter: ns (namespace) object with a commits list + - parameter: attr (string) attribute to sort by + - returns None but modifies the ns.commits list in place +#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) +%}{% set ordered_commits = [] +%}{# + # # Eliminate any ParseError commits from input set +#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list +%}{# + # # grab all commits with no scope and sort alphabetically by attr +#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor +%}{# + # # grab all commits with a scope and sort alphabetically by the scope and then attr +#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor +%}{# + # # Return the ordered commits +#}{% set ns.commits = ordered_commits +%}{% endmacro +%} + + +{# + MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_descriptions(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') +%}{% endmacro +%} + + +{# + MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') +%}{% endmacro +%} + + +{# + MACRO: apply smart ordering of commits objects based on alphabetized release notices and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_release_notices(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') +%}{% endmacro +%} diff --git a/config/release-templates/.components/macros.md.j2 b/config/release-templates/.components/macros.md.j2 index 89cd84cb2..bbccd9c86 100644 --- a/config/release-templates/.components/macros.md.j2 +++ b/config/release-templates/.components/macros.md.j2 @@ -1,33 +1,4 @@ -{% set section_heading_translations = { - 'feat': 'features', - 'fix': 'bug fixes', - 'perf': 'performance improvements', - 'docs': 'documentation', - 'build': 'build system', - 'refactor': 'refactoring', - 'test': 'testing', - 'ci': 'continuous integration', - 'chore': 'chores', - 'style': 'code style', -} %} - -{% set section_heading_order = section_heading_translations.values() %} - -{% set emoji_map = { - 'breaking': '๐Ÿ’ฅ', - 'features': 'โœจ', - 'bug fixes': '๐Ÿชฒ', - 'performance improvements': 'โšก', - 'documentation': '๐Ÿ“–', - 'build system': 'โš™๏ธ', - 'refactoring': 'โ™ป๏ธ', - 'testing': 'โœ…', - 'continuous integration': '๐Ÿค–', - 'chores': '๐Ÿงน', - 'code style': '๐ŸŽจ', - 'unknown': 'โ—', - 'release_note': '๐Ÿ’ก', -} %} +{% from 'macros.common.j2' import capitalize_first_letter_only %} {# @@ -38,14 +9,6 @@ %} -{# - MACRO: Capitalize the first letter of a string only -#}{% macro capitalize_first_letter_only(sentence) -%}{{ (sentence[0] | upper) ~ sentence[1:] -}}{% endmacro -%} - - {# MACRO: commit message links or PR/MR links of commit #}{% macro commit_msg_links(commit) @@ -109,138 +72,3 @@ }}{% endif %}{% endmacro %} - - -{# - MACRO: format the breaking changes description by: - - Capitalizing the description - - Adding an optional scope prefix -#}{% macro format_breaking_changes_description(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.breaking_descriptions -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = capitalize_first_letter_only(paragraph) | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim -%}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) -%}{% endif -%}{% endif -%}{# -#}{{ ns.full_description -}}{% endmacro -%} - - -{# - MACRO: format the release notice by: - - Capitalizing the description - - Adding an optional scope prefix -#}{% macro format_release_notice(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.release_notices -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = capitalize_first_letter_only(paragraph) | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim -%}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) -%}{% endif -%}{% endif -%}{# -#}{{ ns.full_description -}}{% endmacro -%} - - -{# - MACRO: order commits alphabetically by scope and attribute - - Commits are sorted based on scope and then the attribute alphabetically - - Commits without scope are placed first and sorted alphabetically by the attribute - - parameter: ns (namespace) object with a commits list - - parameter: attr (string) attribute to sort by - - returns None but modifies the ns.commits list in place -#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by attr -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) -%}{% set _ = ordered_commits.append(commit) -%}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then attr -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) -%}{% set _ = ordered_commits.append(commit) -%}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_descriptions(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized release notices and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_release_notices(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') -%}{% endmacro -%} diff --git a/config/release-templates/.components/macros.rst.j2 b/config/release-templates/.components/macros.rst.j2 index 779571c02..11c61d6de 100644 --- a/config/release-templates/.components/macros.rst.j2 +++ b/config/release-templates/.components/macros.rst.j2 @@ -1,35 +1,5 @@ -{# TODO: move to configuration for user to modify #} -{% set section_heading_translations = { - 'feat': 'features', - 'fix': 'bug fixes', - 'perf': 'performance improvements', - 'docs': 'documentation', - 'build': 'build system', - 'refactor': 'refactoring', - 'test': 'testing', - 'ci': 'continuous integration', - 'chore': 'chores', - 'style': 'code style', - } -%} - -{% set section_heading_order = section_heading_translations.values() %} +{% from 'macros.common.j2' import capitalize_first_letter_only %} -{% set emoji_map = { - 'breaking': '๐Ÿ’ฅ', - 'features': 'โœจ', - 'bug fixes': '๐Ÿชฒ', - 'performance improvements': 'โšก', - 'documentation': '๐Ÿ“–', - 'build system': 'โš™๏ธ', - 'refactoring': 'โ™ป๏ธ', - 'testing': 'โœ…', - 'continuous integration': '๐Ÿค–', - 'chores': '๐Ÿงน', - 'code style': '๐ŸŽจ', - 'unknown': 'โ—', - 'release_note': '๐Ÿ’ก', -} %} {# MACRO: format a post-paragraph link reference in RST @@ -39,79 +9,18 @@ %} -{# - MACRO: Capitalize the first letter of a string only -#}{% macro capitalize_first_letter_only(sentence) -%}{{ (sentence[0] | upper) ~ sentence[1:] +{# MACRO: generate a heading underline that matches the exact length of the header #} +{% macro generate_heading_underline(header, underline_char) +%}{% set header_underline = [] +%}{% for _ in header +%}{% set __ = header_underline.append(underline_char) +%}{% endfor +%}{# # Print out the header underline +#}{{ header_underline | join }}{% endmacro %} -{# - MACRO: format commit summary line -#}{% macro format_commit_summary_line(commit) -%}{# # Check for Parsing Error -#}{% if commit.error is undefined -%}{# - # # Add any message links to the commit summary line -#}{% set summary_line = commit_msg_links(commit) -%}{# -#}{% if commit.scope -%}{% set summary_line = "**%s**: %s" | format(commit.scope, summary_line) -%}{% endif -%}{# - # # Return the modified summary_line -#}{{ summary_line -}}{# -#}{% else -%}{# # Return the first line of the commit if there was a Parsing Error -#}{{ (commit.commit.message | string).split("\n", maxsplit=1)[0] -}}{% endif -%}{% endmacro -%} - - -{# - MACRO: Create & return an non-inline RST link from a commit message - - Returns empty string if no PR/MR identifier is found -#}{% macro extract_pr_link_reference(commit) -%}{% if commit.error is undefined -%}{% set summary_line = commit.descriptions[0] -%}{# -#}{% if commit.linked_merge_request != "" -%}{# # Create a PR/MR reference url -#}{{ format_link_reference( - commit.linked_merge_request | pull_request_url, - "PR" ~ commit.linked_merge_request, - ) -}}{% endif -%}{% endif -%}{% endmacro -%} - -{# - MACRO: Extract issue references from a parsed commit object - - Stores the issue urls in the namespace object -#}{% macro extract_issue_link_references(ns, commit) -%}{% set issue_urls = [] -%}{# -#}{% if commit.linked_issues is defined and commit.linked_issues | length > 0 -%}{% for issue_num in commit.linked_issues -%}{# # Create an issue reference url -#}{% set _ = issue_urls.append( - format_link_reference( - issue_num | issue_url, - issue_num, - ) - ) -%}{% endfor -%}{% endif -%}{# - # # Store the issue urls in the namespace object -#}{% set ns.urls = issue_urls -%}{% endmacro -%} - {# MACRO: formats a commit message for a non-inline RST link for a commit hash and/or PR/MR #}{% macro commit_msg_links(commit) @@ -150,148 +59,68 @@ %} -{# MACRO: generate a heading underline that matches the exact length of the header #} -{% macro generate_heading_underline(header, underline_char) -%}{% set header_underline = [] -%}{% for _ in header -%}{% set __ = header_underline.append(underline_char) -%}{% endfor -%}{# # Print out the header underline -#}{{ header_underline | join -}}{% endmacro -%} - - {# - MACRO: format the breaking changes description by: - - Capitalizing the description - - Adding an optional scope prefix -#}{% macro format_breaking_changes_description(commit) -%}{% set ns = namespace(full_description="") -%}{# + MACRO: format commit summary line +#}{% macro format_commit_summary_line(commit) +%}{# # Check for Parsing Error #}{% if commit.error is undefined -%}{% for paragraph in commit.breaking_descriptions -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = capitalize_first_letter_only(paragraph) | trim | safe %}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim + # # Add any message links to the commit summary line +#}{% set summary_line = commit_msg_links(commit) %}{# #}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) +%}{% set summary_line = "**%s**: %s" | format(commit.scope, summary_line) %}{% endif -%}{% endif %}{# -#}{{ ns.full_description -}}{% endmacro + # # Return the modified summary_line +#}{{ summary_line +}}{# +#}{% else +%}{# # Return the first line of the commit if there was a Parsing Error +#}{{ (commit.commit.message | string).split("\n", maxsplit=1)[0] +}}{% endif +%}{% endmacro %} {# - MACRO: format the release notice by: - - Capitalizing the description - - Adding an optional scope prefix -#}{% macro format_release_notice(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.release_notices -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = capitalize_first_letter_only(paragraph) | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim + MACRO: Extract issue references from a parsed commit object + - Stores the issue urls in the namespace object +#}{% macro extract_issue_link_references(ns, commit) +%}{% set issue_urls = [] %}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description +#}{% if commit.linked_issues is defined and commit.linked_issues | length > 0 +%}{% for issue_num in commit.linked_issues +%}{# # Create an issue reference url +#}{% set _ = issue_urls.append( + format_link_reference( + issue_num | issue_url, + issue_num, + ) ) -%}{% endif +%}{% endfor %}{% endif %}{# -#}{{ ns.full_description -}}{% endmacro -%} - - -{# - MACRO: order commits alphabetically by scope and attribute - - Commits are sorted based on scope and then the attribute alphabetically - - Commits without scope are placed first and sorted alphabetically by the attribute - - parameter: ns (namespace) object with a commits list - - parameter: attr (string) attribute to sort by - - returns None but modifies the ns.commits list in place -#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by attr -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) -%}{% set _ = ordered_commits.append(commit) -%}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then attr -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) -%}{% set _ = ordered_commits.append(commit) -%}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_descriptions(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') + # # Store the issue urls in the namespace object +#}{% set ns.urls = issue_urls %}{% endmacro %} {# - MACRO: apply smart ordering of commits objects based on alphabetized release notices and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_release_notices(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') + MACRO: Create & return an non-inline RST link from a commit message + - Returns empty string if no PR/MR identifier is found +#}{% macro extract_pr_link_reference(commit) +%}{% if commit.error is undefined +%}{% set summary_line = commit.descriptions[0] +%}{# +#}{% if commit.linked_merge_request != "" +%}{# # Create a PR/MR reference url +#}{{ format_link_reference( + commit.linked_merge_request | pull_request_url, + "PR" ~ commit.linked_merge_request, + ) +}}{% endif +%}{% endif %}{% endmacro %} From ce311adc300992642676cd27c58d39ce28513131 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 26 May 2025 14:01:11 -0600 Subject: [PATCH 05/13] chore: update in code todos for v10 (#1266) --- src/semantic_release/cli/changelog_writer.py | 2 +- src/semantic_release/cli/config.py | 10 +++++----- src/semantic_release/commit_parser/_base.py | 2 +- src/semantic_release/commit_parser/angular.py | 11 ----------- src/semantic_release/commit_parser/scipy.py | 4 ++-- src/semantic_release/commit_parser/token.py | 2 +- src/semantic_release/commit_parser/util.py | 2 +- .../commit_parser/test_conventional.py | 14 ++++++-------- .../semantic_release/commit_parser/test_emoji.py | 14 ++++++-------- .../semantic_release/commit_parser/test_scipy.py | 14 ++++++-------- 10 files changed, 29 insertions(+), 46 deletions(-) diff --git a/src/semantic_release/cli/changelog_writer.py b/src/semantic_release/cli/changelog_writer.py index 96020b73a..65e387896 100644 --- a/src/semantic_release/cli/changelog_writer.py +++ b/src/semantic_release/cli/changelog_writer.py @@ -269,7 +269,7 @@ def generate_release_notes( environment(autoescape=False, template_dir=tpl_dir) ) - # TODO: Remove in v10 + # TODO: Remove in v11 release_notes_env.globals["context"] = release_notes_env.globals["ctx"] = { "history": history, "mask_initial_release": mask_initial_release, diff --git a/src/semantic_release/cli/config.py b/src/semantic_release/cli/config.py index 59df69834..1d2057a48 100644 --- a/src/semantic_release/cli/config.py +++ b/src/semantic_release/cli/config.py @@ -148,7 +148,7 @@ def interpret_output_format(self) -> Self: class ChangelogConfig(BaseModel): - # TODO: BREAKING CHANGE v10, move to DefaultChangelogTemplatesConfig + # TODO: BREAKING CHANGE v11, move to DefaultChangelogTemplatesConfig changelog_file: str = "" """Deprecated! Moved to 'default_templates.changelog_file'""" @@ -191,7 +191,7 @@ def changelog_file_deprecation_warning(cls, val: str) -> str: @model_validator(mode="after") def move_changelog_file(self) -> Self: - # TODO: Remove this method in v10 + # TODO: Remove this method in v11 if not self.changelog_file: return self @@ -441,7 +441,7 @@ def set_default_opts(self) -> Self: parser_opts_type = None # If the commit parser is a known one, pull the default options object from it if self.commit_parser in _known_commit_parsers: - # TODO: BREAKING CHANGE v10 + # TODO: BREAKING CHANGE v11 # parser_opts_type = ( # _known_commit_parsers[self.commit_parser] # .get_default_options() @@ -454,7 +454,7 @@ def set_default_opts(self) -> Self: try: # if its a custom parser, try to import it and pull the default options object type custom_class = dynamic_import(self.commit_parser) - # TODO: BREAKING CHANGE v10 + # TODO: BREAKING CHANGE v11 # parser_opts_type = custom_class.get_default_options().__class__ if hasattr(custom_class, "parser_options"): parser_opts_type = custom_class.parser_options @@ -695,7 +695,7 @@ def from_raw_config( # noqa: C901 ) from err commit_parser_opts_class = commit_parser_cls.parser_options - # TODO: Breaking change v10 + # TODO: Breaking change v11 # commit_parser_opts_class = commit_parser_cls.get_default_options().__class__ try: commit_parser = commit_parser_cls( diff --git a/src/semantic_release/commit_parser/_base.py b/src/semantic_release/commit_parser/_base.py index 04d2f56bd..a144e0945 100644 --- a/src/semantic_release/commit_parser/_base.py +++ b/src/semantic_release/commit_parser/_base.py @@ -74,7 +74,7 @@ def __init__(self, options: _OPTS | None = None) -> None: options if options is not None else self.get_default_options() ) - # TODO: BREAKING CHANGE v10, add abstract method for all custom parsers + # TODO: BREAKING CHANGE v11, add abstract method for all custom parsers # @staticmethod # @abstractmethod def get_default_options(self) -> _OPTS: diff --git a/src/semantic_release/commit_parser/angular.py b/src/semantic_release/commit_parser/angular.py index eeef82796..411ac844b 100644 --- a/src/semantic_release/commit_parser/angular.py +++ b/src/semantic_release/commit_parser/angular.py @@ -94,11 +94,9 @@ class AngularParserOptions(ParserOptions): default_bump_level: LevelBump = LevelBump.NO_RELEASE """The minimum bump level to apply to valid commit message.""" - # TODO: breaking change v10, change default to True parse_squash_commits: bool = False """Toggle flag for whether or not to parse squash commits""" - # TODO: breaking change v10, change default to True ignore_merge_commits: bool = False """Toggle flag for whether or not to ignore merge commits""" @@ -236,15 +234,11 @@ def commit_body_components_separator( ) -> dict[str, list[str]]: if (match := breaking_re.match(text)) and (brk_desc := match.group(1)): accumulator["breaking_descriptions"].append(brk_desc) - # TODO: breaking change v10, removes breaking change footers from descriptions - # return accumulator elif (match := self.notice_selector.match(text)) and ( notice := match.group("notice") ): accumulator["notices"].append(notice) - # TODO: breaking change v10, removes notice footers from descriptions - # return accumulator elif match := self.issue_selector.search(text): # if match := self.issue_selector.search(text): @@ -265,8 +259,6 @@ def commit_body_components_separator( accumulator["linked_issues"] = sort_numerically( set(accumulator["linked_issues"]).union(new_issue_refs) ) - # TODO: breaking change v10, removes resolution footers from descriptions - # return accumulator # Prevent appending duplicate descriptions if text not in accumulator["descriptions"]: @@ -287,9 +279,6 @@ def parse_message(self, message: str) -> ParsedMessageResult | None: linked_merge_request = "" if mr_match := self.mr_selector.search(parsed_subject): linked_merge_request = mr_match.group("mr_number") - # TODO: breaking change v10, removes PR number from subject/descriptions - # expects changelog template to format the line accordingly - # parsed_subject = self.pr_selector.sub("", parsed_subject).strip() body_components: dict[str, list[str]] = reduce( self.commit_body_components_separator, diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index 7e0e6b246..e6988ea83 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -145,7 +145,7 @@ class ScipyParserOptions(ParserOptions): one of these prefixes, it will not be considered a valid commit message. """ - # TODO: breaking v10, make consistent with AngularParserOptions + # TODO: breaking v11, make consistent with AngularParserOptions default_level_bump: LevelBump = LevelBump.NO_RELEASE """The minimum bump level to apply to valid commit message.""" @@ -161,7 +161,7 @@ def tag_to_level(self) -> dict[str, LevelBump]: return self._tag_to_level def __post_init__(self) -> None: - # TODO: breaking v10, remove as the name is now consistent + # TODO: breaking v11, remove as the name is now consistent self.default_bump_level = self.default_level_bump self._tag_to_level: dict[str, LevelBump] = { str(tag): level diff --git a/src/semantic_release/commit_parser/token.py b/src/semantic_release/commit_parser/token.py index a9bb254de..332f4283b 100644 --- a/src/semantic_release/commit_parser/token.py +++ b/src/semantic_release/commit_parser/token.py @@ -150,7 +150,7 @@ def from_parsed_message_result( """A convience method to create a ParsedCommit object from a ParsedMessageResult object and a Commit object.""" return ParsedCommit( bump=parsed_message_result.bump, - # TODO: breaking v10, swap back to type rather than category + # TODO: breaking v11, swap back to type rather than category type=parsed_message_result.category, scope=parsed_message_result.scope, descriptions=list(parsed_message_result.descriptions), diff --git a/src/semantic_release/commit_parser/util.py b/src/semantic_release/commit_parser/util.py index 9c1322b41..258e8224b 100644 --- a/src/semantic_release/commit_parser/util.py +++ b/src/semantic_release/commit_parser/util.py @@ -6,7 +6,7 @@ from re import MULTILINE, compile as regexp from typing import TYPE_CHECKING -# TODO: remove in v10 +# TODO: remove in v11 from semantic_release.helpers import ( sort_numerically, # noqa: F401 # TODO: maintained for compatibility ) diff --git a/tests/unit/semantic_release/commit_parser/test_conventional.py b/tests/unit/semantic_release/commit_parser/test_conventional.py index 02cd4f5de..9298c1f24 100644 --- a/tests/unit/semantic_release/commit_parser/test_conventional.py +++ b/tests/unit/semantic_release/commit_parser/test_conventional.py @@ -205,7 +205,7 @@ def test_parser_squashed_commit_bitbucket_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -388,7 +388,7 @@ def test_parser_squashed_commit_git_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -549,7 +549,7 @@ def test_parser_squashed_commit_github_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -699,7 +699,6 @@ def test_parser_return_subject_from_commit_message( @pytest.mark.parametrize( "message, subject, merge_request_number", - # TODO: in v10, we will remove the merge request number from the subject line [ # GitHub, Gitea style ( @@ -1109,10 +1108,9 @@ def test_parser_return_release_notices_from_commit_message( assert isinstance(result, ParsedCommit) assert tuple(notices) == result.release_notices - # TODO: v10, remove this - # full_description = str.join("\n\n", result.descriptions) - # full_notice = str.join("\n\n", result.release_notices) - # assert full_notice not in full_description + full_description = str.join("\n\n", result.descriptions) + full_notice = str.join("\n\n", result.release_notices) + assert full_notice not in full_description ############################## diff --git a/tests/unit/semantic_release/commit_parser/test_emoji.py b/tests/unit/semantic_release/commit_parser/test_emoji.py index ac7708ebb..c477579ec 100644 --- a/tests/unit/semantic_release/commit_parser/test_emoji.py +++ b/tests/unit/semantic_release/commit_parser/test_emoji.py @@ -136,7 +136,6 @@ def test_parser_return_linked_merge_request_from_commit_message( @pytest.mark.parametrize( "message, linked_issues", - # TODO: in v10, we will remove the issue reference footers from the descriptions [ *[ # GitHub, Gitea, GitLab style @@ -510,10 +509,9 @@ def test_parser_return_release_notices_from_commit_message( assert isinstance(result, ParsedCommit) assert tuple(notices) == result.release_notices - # TODO: v10, remove this - # full_description = str.join("\n\n", result.descriptions) - # full_notice = str.join("\n\n", result.release_notices) - # assert full_notice not in full_description + full_description = str.join("\n\n", result.descriptions) + full_notice = str.join("\n\n", result.release_notices) + assert full_notice not in full_description @pytest.mark.parametrize( @@ -689,7 +687,7 @@ def test_parser_squashed_commit_bitbucket_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -878,7 +876,7 @@ def test_parser_squashed_commit_git_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -1042,7 +1040,7 @@ def test_parser_squashed_commit_github_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues diff --git a/tests/unit/semantic_release/commit_parser/test_scipy.py b/tests/unit/semantic_release/commit_parser/test_scipy.py index 46f70b211..ce714c0bc 100644 --- a/tests/unit/semantic_release/commit_parser/test_scipy.py +++ b/tests/unit/semantic_release/commit_parser/test_scipy.py @@ -615,7 +615,7 @@ def test_parser_squashed_commit_bitbucket_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -798,7 +798,7 @@ def test_parser_squashed_commit_git_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -959,7 +959,7 @@ def test_parser_squashed_commit_github_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -968,7 +968,6 @@ def test_parser_squashed_commit_github_squash_style( @pytest.mark.parametrize( "message, linked_issues", - # TODO: in v10, we will remove the issue reference footers from the descriptions [ *[ # GitHub, Gitea, GitLab style @@ -1331,10 +1330,9 @@ def test_parser_return_release_notices_from_commit_message( assert isinstance(result, ParsedCommit) assert tuple(notices) == result.release_notices - # TODO: v10, remove this - # full_description = str.join("\n\n", result.descriptions) - # full_notice = str.join("\n\n", result.release_notices) - # assert full_notice not in full_description + full_description = str.join("\n\n", result.descriptions) + full_notice = str.join("\n\n", result.release_notices) + assert full_notice not in full_description def test_parser_ignore_merge_commit( From bb8f2ee237ef1269cbf0e4f8137b3a099d8b9ea0 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 7 Jun 2025 15:06:53 -0600 Subject: [PATCH 06/13] chore(config): add version stamp to github-actions examples in docs (#1272) * chore(scripts): drop code to bump versions of github-action examples in documentation --- pyproject.toml | 2 ++ scripts/bump_version_in_docs.py | 24 ------------------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7a3aa918a..8dc8935a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -414,6 +414,8 @@ build_command = """ major_on_zero = true version_variables = [ "src/gh_action/requirements.txt:python-semantic-release:nf", + "docs/configuration/automatic-releases/github-actions.rst:python-semantic-release/python-semantic-release:tf", + "docs/configuration/automatic-releases/github-actions.rst:python-semantic-release/publish-action:tf", ] version_toml = ["pyproject.toml:project.version"] diff --git a/scripts/bump_version_in_docs.py b/scripts/bump_version_in_docs.py index 7c6104791..3155cd0c7 100644 --- a/scripts/bump_version_in_docs.py +++ b/scripts/bump_version_in_docs.py @@ -12,25 +12,6 @@ tag_replace_pattern = regexp(r"\$(NEW_RELEASE_TAG|{NEW_RELEASE_TAG})") -def update_github_actions_example(filepath: Path, release_tag: str) -> None: - psr_regex = regexp(r"(uses: python-semantic-release/python-semantic-release)@\S+$") - psr_publish_action_regex = regexp( - r"(uses: python-semantic-release/publish-action)@\S+$" - ) - file_content_lines: list[str] = filepath.read_text().splitlines() - - for regex in [psr_regex, psr_publish_action_regex]: - file_content_lines = list( - map( - lambda line, regex=regex: regex.sub(r"\1@" + release_tag, line), # type: ignore[misc] - file_content_lines, - ) - ) - - print(f"Bumping version in {filepath} to", release_tag) - filepath.write_text(str.join("\n", file_content_lines) + "\n") - - def envsubst(filepath: Path, version: str, release_tag: str) -> None: file_content = filepath.read_text() @@ -59,10 +40,5 @@ def envsubst(filepath: Path, version: str, release_tag: str) -> None: print("NEW_VERSION environment variable is not set") exit(1) - update_github_actions_example( - DOCS_DIR / "configuration" / "automatic-releases" / "github-actions.rst", - new_release_tag, - ) - for doc_file in DOCS_DIR.rglob("*.rst"): envsubst(filepath=doc_file, version=new_version, release_tag=new_release_tag) From fbb63ec76142ea903d8a0401369ec251abbec0fe Mon Sep 17 00:00:00 2001 From: Bart Dorlandt Date: Sat, 7 Jun 2025 23:12:09 +0200 Subject: [PATCH 07/13] docs(github-actions): clarify with examples of the `root_options` v10 migration change (#1271) --- .../automatic-releases/github-actions.rst | 16 +++---- docs/upgrading/10-upgrade.rst | 47 ++++++++++++++++--- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index 4fc5022af..93024e3dd 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -371,9 +371,9 @@ to the remote repository. This option is equivalent to adding either ``--push`` """""""""""""""" .. important:: - This option has been removed in v10.0.0 and newer because of a - command injection vulnerability. Please update as to v10.0.0 as soon - as possible. + This option has been removed in v10.0.0 and newer because of a command injection + vulnerability. Please update as to v10.0.0 as soon as possible. See + :ref:`Upgrading to v10 ` for more information. Additional options for the main ``semantic-release`` command, which will come before the :ref:`version ` subcommand. @@ -382,7 +382,7 @@ before the :ref:`version ` subcommand. .. code:: yaml - - uses: python-semantic-release/python-semantic-release@v10.0.2 + - uses: python-semantic-release/python-semantic-release@v9 with: root_options: "-vv --noop" @@ -688,9 +688,9 @@ This is useful for testing the action without actually publishing anything. """""""""""""""" .. important:: - This option has been removed in v10.0.0 and newer because of a - command injection vulnerability. Please update as to v10.0.0 as soon - as possible. + This option has been removed in v10.0.0 and newer because of a command injection + vulnerability. Please update as to v10.0.0 as soon as possible. See + :ref:`Upgrading to v10 ` for more information. Additional options for the main ``semantic-release`` command, which will come before the :ref:`publish ` subcommand. @@ -699,7 +699,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v10.0.2 + - uses: python-semantic-release/publish-action@v9 with: root_options: "-vv --noop" diff --git a/docs/upgrading/10-upgrade.rst b/docs/upgrading/10-upgrade.rst index ffd6b0276..7cb4e03be 100644 --- a/docs/upgrading/10-upgrade.rst +++ b/docs/upgrading/10-upgrade.rst @@ -42,18 +42,51 @@ This vulnerability existed in both the For the main :ref:`python-semantic-release/python-semantic-release ` action, the following inputs are now available (in place of the old ``root_options`` parameter): +:ref:`gh_actions-psr-inputs-config_file`, :ref:`gh_actions-psr-inputs-noop`, +:ref:`gh_actions-psr-inputs-strict`, and :ref:`gh_actions-psr-inputs-verbosity`. -- :ref:`gh_actions-psr-inputs-config_file` -- :ref:`gh_actions-psr-inputs-noop` -- :ref:`gh_actions-psr-inputs-strict` -- :ref:`gh_actions-psr-inputs-verbosity` + **Example migration** + + If you previously had the following in your GitHub Actions workflow file: + + .. code:: yaml + + - uses: python-semantic-release/python-semantic-release@v9 + with: + root_options: "-vv --strict" + + It would be updated to: + + .. code:: yaml + + - uses: python-semantic-release/python-semantic-release@v10 + with: + strict: true + verbosity: 2 For the :ref:`python-semantic-release/publish-action ` action, the following inputs are now available (in place of the old ``root_options`` parameter): +:ref:`gh_actions-publish-inputs-config_file`, :ref:`gh_actions-publish-inputs-noop`, +and :ref:`gh_actions-publish-inputs-verbosity`. + + **Example migration** + + If you previously had the following in your GitHub Actions workflow file: + + .. code:: yaml + + - uses: python-semantic-release/publish-action@v9 + with: + root_options: "-v -c /path/to/releaserc.yaml" + + It would be updated to: + + .. code:: yaml -- :ref:`gh_actions-publish-inputs-config_file` -- :ref:`gh_actions-publish-inputs-noop` -- :ref:`gh_actions-publish-inputs-verbosity` + - uses: python-semantic-release/publish-action@v10 + with: + config_file: /path/to/releaserc.yaml + verbosity: 1 .. _upgrade_v10-changelog_format-1_line_commit_subjects: From b31b5405d0f40f85af83e16ad7e6b78999319b6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Jun 2025 14:35:18 -0700 Subject: [PATCH 08/13] build(deps-dev): bump mypy from 1.15.0 to 1.16.0 (#1270) * chore(config): update mypy version in `pre-commit` configuration --- .pre-commit-config.yaml | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f80f12a05..3f68bac6c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,14 +50,14 @@ repos: name: ruff (format) - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.15.0" + rev: "v1.16.0" hooks: - id: mypy additional_dependencies: - "pydantic>=2,<3" - "types-requests" log_file: "mypy.log" - files: "^src/.*" + files: "^(src|tests)/.*" pass_filenames: false - repo: https://github.com/pre-commit/pygrep-hooks diff --git a/pyproject.toml b/pyproject.toml index 8dc8935a4..df2551f80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ dev = [ "ruff == 0.6.1" ] mypy = [ - "mypy == 1.15.0", + "mypy == 1.16.0", "types-Deprecated ~= 1.2", "types-requests ~= 2.32.0", "types-pyyaml ~= 6.0", From 8da19f113f4f00163ef6f30f14e9b430d4476fc0 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 7 Jun 2025 15:26:04 -0600 Subject: [PATCH 09/13] ci(deps): bump `mikepenz/action-junit-report@v5.5.1` action to `v5.6.0` --- .github/workflows/validate.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index f20aa9700..bb8df05c1 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -195,7 +195,7 @@ jobs: --junit-xml=tests/reports/pytest-results.xml - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@cf701569b05ccdd861a76b8607a66d76f6fd4857 # v5.5.1 + uses: mikepenz/action-junit-report@65fe03598d8d251738592a497a9e8547a5c48eaa # v5.6.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -285,7 +285,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@cf701569b05ccdd861a76b8607a66d76f6fd4857 # v5.5.1 + uses: mikepenz/action-junit-report@65fe03598d8d251738592a497a9e8547a5c48eaa # v5.6.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -383,7 +383,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@cf701569b05ccdd861a76b8607a66d76f6fd4857 # v5.5.1 + uses: mikepenz/action-junit-report@65fe03598d8d251738592a497a9e8547a5c48eaa # v5.6.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml From f772121f4f07000e6cb8e720c50c5f2662fb67b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 18:32:34 +0000 Subject: [PATCH 10/13] ci(deps): bump `docker/build-push-action@v6.17.0` to `v6.18.0` --- .github/workflows/validate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index bb8df05c1..f8487cf39 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -428,7 +428,7 @@ jobs: - name: Build | Action Container id: container-builder - uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: ${{ env.ACTION_SRC_DIR }} load: true # add to `docker images` From 99fc9ccabbae9adf5646731591080366eacbe03c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 20:47:03 -0700 Subject: [PATCH 11/13] build(deps): expand `python-gitlab` dependency to include `v6.0.0` (#1273) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index df2551f80..5d4f90314 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "gitpython ~= 3.0", "requests ~= 2.25", "jinja2 ~= 3.1", - "python-gitlab >= 4.0.0, < 6.0.0", + "python-gitlab >= 4.0.0, < 7.0.0", "tomlkit ~= 0.11", "dotty-dict ~= 1.3", "importlib-resources ~= 6.0", From de623344cd18b3dbe05823eb90fdd010c5505c92 Mon Sep 17 00:00:00 2001 From: JonZeolla Date: Thu, 12 Jun 2025 00:02:46 -0400 Subject: [PATCH 12/13] feat(cmd-version): always stage version stamped files & changelog even with `--no-commit` (#1214) Resolves: #1211 * test(cmd-version): update version stamp test to verify changes are staged during execution * docs(configuration-guide): add how-to guide for `uv` integration * docs(cmd-version): improve command description & include common uses --------- Co-authored-by: codejedi365 --- docs/api/commands.rst | 77 ++++- .../configuration-guides/index.rst | 14 + .../configuration-guides/uv_integration.rst | 327 ++++++++++++++++++ docs/configuration/index.rst | 16 +- src/semantic_release/cli/commands/version.py | 17 +- tests/e2e/cmd_version/test_version_stamp.py | 11 +- 6 files changed, 426 insertions(+), 36 deletions(-) create mode 100644 docs/configuration/configuration-guides/index.rst create mode 100644 docs/configuration/configuration-guides/uv_integration.rst diff --git a/docs/api/commands.rst b/docs/api/commands.rst index 344c7f5f4..3dca77474 100644 --- a/docs/api/commands.rst +++ b/docs/api/commands.rst @@ -96,25 +96,73 @@ pipeline, while omitting this flag would allow the pipeline to continue to run. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Detect the semantically correct next version that should be applied to your -project. +project and release it. -By default: +By default (in order): - * Write this new version to the project metadata locations - specified in the configuration file - * Build the project using :ref:`config-build_command`, if specified - * Create a new commit with these locations and any other assets configured - to be included in a release - * Tag this commit according the configured format, with a tag that uniquely - identifies the version being released - * Push the new tag and commit to the remote for the repository - * Create a release (if supported) in the remote VCS for this tag + #. Write this new version to the project metadata locations + specified in the configuration file + + #. Update the changelog file with the new version and any changes + introduced since the last release, using the configured changelog template + + #. Build the project using :ref:`config-build_command`, if specified + + #. Create a new commit with these locations and any other assets configured + to be included in a release + + #. Tag this commit according the configured format, with a tag that uniquely + identifies the version being released + + #. Push the new tag and commit to the remote for the repository + + #. Create a release in the remote VCS for this tag (if supported) + +All of these steps can be toggled on or off using the command line options +described below. Some of the steps rely on others, so some options may implicitly +disable others. Changelog generation is done identically to the way it is done in :ref:`cmd-changelog`, but this command additionally ensures the updated changelog is included in the release commit that is made. + **Common Variations** + + .. code-block:: bash + + # Print the next version that will be applied + semantic-release version --print + + # Print the next version that will be applied, including the tag prefix + semantic-release version --print-tag + + # Print the last released version + semantic-release version --print-last-released + + # Print the last released version, including the tag prefix + semantic-release version --print-last-released-tag + + # Only stamp the next version in the project metadata locations + semantic-release version --no-changelog --skip-build --no-commit --no-tag + + # Stamp the version, update the changelog, and run the build command, then stop + semantic-release version --no-commit --no-tag + + # Make all local changes but do not publish them to the remote (changelog, build, commit & tag) + semantic-release version --no-push + + # Don't ever create a changelog (but do everything else) + semantic-release version --no-changelog + + # Don't create a release in the remote VCS (but do publish the commit and tag) + semantic-release version --no-vcs-release + + # Do everything + semantic-release version + + .. seealso:: + - :ref:`Ultraviolet (uv) integration ` - :ref:`cmd-changelog` - :ref:`changelog-templates` - :ref:`config-tag_format` @@ -122,6 +170,7 @@ commit that is made. - :ref:`config-version_toml` - :ref:`config-version_variables` + .. _cmd-version-options: Options: @@ -362,9 +411,9 @@ Whether or not to push new commits and/or tags to the remote repository. ``--vcs-release/--no-vcs-release`` ********************************** -Whether or not to create a "release" in the remote VCS service, if supported. Currently -releases in GitHub and Gitea remotes are supported. If releases aren't supported in a -remote VCS, this option will not cause a command failure, but will produce a warning. +Whether or not to create a "release" in the remote VCS service, if supported. If +releases aren't supported in a remote VCS, this option will not cause a command +failure, but will produce a warning. **Default:** ``--no-vcs-release`` if ``--no-push`` is supplied (including where this is implied by supplying only ``--no-commit``), otherwise ``--vcs-release`` diff --git a/docs/configuration/configuration-guides/index.rst b/docs/configuration/configuration-guides/index.rst new file mode 100644 index 000000000..70024dd11 --- /dev/null +++ b/docs/configuration/configuration-guides/index.rst @@ -0,0 +1,14 @@ +.. _config-guides: + +Configuration Guides +==================== + +This section provides detailed guides on how to configure PSR for various use cases and +integrations. It is recommended to complete the +:ref:`Getting Started Guide ` first before diving into these +more specific configurations. + +.. toctree:: + :maxdepth: 1 + + UV Project Setup diff --git a/docs/configuration/configuration-guides/uv_integration.rst b/docs/configuration/configuration-guides/uv_integration.rst new file mode 100644 index 000000000..cb31e79c1 --- /dev/null +++ b/docs/configuration/configuration-guides/uv_integration.rst @@ -0,0 +1,327 @@ +.. _config-guides-uv_integration: + +Ultraviolet (``uv``) Integration +================================ + +.. _uv: https://docs.astral.sh/uv/ + +`uv`_ is an extremely fast Python package and project manager that +provides a modern alternative to `pip `_ +and `venv `_. It provides a lot +of features that solve the common problems of Python package management but +it also introduces a few quirks that need to be taken into account when using +Python Semantic Release. + +.. important:: + + **Prerequisite:** Make sure you have run through the + :ref:`Getting Started Guide ` before proceeding with + this guide. + + +Updating the ``uv.lock`` +------------------------ + +One of the best features of ``uv`` is that it automatically generates a lock file +(``uv.lock``) that contains the exact versions of all the dependencies used in +your project. The lock file is generated when you run the ``uv install`` command, +and it is used to ensure that CI workflows are repeatable and development environments +are consistent. + +When creating a new release using Python Semantic Release, PSR will update the version +in the project's definition file (e.g., ``pyproject.toml``) to indicate the new version. +Unfortunately, this action will cause ``uv`` to fail on the next execution because the +lock file will be out of sync with the project's definition file. There are two ways to +resolve this issue depending on your preference: + +#. **Add a step to your build command**: Modify your + :ref:`semantic_release.build_command ` to include the command + to update the lock file and stage it for commit. This is commonly used with the + :ref:`GitHub Action ` and other CI/CD tools when you are building + the artifact at the time of release. + + .. code-block:: toml + + [tool.semantic_release] + build_command = """ + uv lock --offline + git add uv.lock + uv build + """ + + The ``--offline`` flag is used to ensure that the lock file is updated without + updating any dependency versions. The intent of this call is **ONLY** to update + the version of your project within the lock file after PSR has updated the version + in your project's definition file (e.g., ``pyproject.toml``). When you are running + PSR, you have already tested the project as is and you don't want to actually + update the dependencies if a new one just became available. + + If you are using the :ref:`PSR GitHub Action `, you will need to add an + installation command for ``uv`` to the :ref:`build_command ` + because the action runs in a Docker environment does not include ``uv`` by default. + The best way to ensure that the correct version of ``uv`` is installed is to define + the version of ``uv`` in an optional dependency list (e.g. ``build``). This will + also help with other automated tools like Dependabot or Renovate to keep the version + of ``uv`` up to date. + + .. code-block:: toml + + [project.optional-dependencies] + build = ["uv ~= 0.7.12"] + + [tool.semantic_release] + build_command = """ + python -m pip install -e .[build] + uv lock --offline + git add uv.lock + uv build + """ + +#. **Stamp the code first & then separately run release**: If you prefer to not modify the + build command, then you will need to run the ``uv lock --offline`` command prior to actually + creating the release. Essentially, you will run PSR twice: (1) once to update the version + in the project's definition file, and (2) a second time to generate the release. + + The intent of the ``uv lock --offline`` command is **ONLY** to update the version of your + project within the lock file after PSR has updated the version in your project's definition + file (e.g., ``pyproject.toml``). When you are running PSR, you have already tested the project + as is and you don't want to actually update the dependencies if a new one just became available. + + .. code-block:: bash + + # 1. PSR stamps version into files (nothing else) + # don't build the changelog (especially in update mode) + semantic-release -v version --skip-build --no-commit --no-tag --no-changelog + + # 2. run UV lock as pyproject.toml is updated with the next version + uv lock --offline + + # 3. stage the lock file to ensure it is included in the PSR commit + git add uv.lock + + # 4. run PSR fully to create release + semantic-release -v version + +**Advanced Example** + +Of course, you can mix and match these 2 approaches as needed. If PSR's pipeline was using +``uv``, we would have a mixture of the 2 approaches because we run the build in a separate +job from the release. In our case, PSR would also need to carry the lock file as a workflow +artifact along the pipeline for the release job to commit it. This advanced workflow would +look like this: + +.. code-block:: text + + # File: .tool-versions + uv 0.7.12 + +.. code-block:: text + + # File: .python-version + 3.11.11 + +.. code-block:: toml + + # File: pyproject.toml + [project.optional-dependencies] + build = ["python-semantic-release ~= 10.0"] + test = ["pytest ~= 8.0"] + + [tool.semantic_release] + build_command = """ + uv lock --offline + uv build + """ + +.. code-block:: yaml + + # File: .github/workflows/release.yml + on: + push: + branches: + - main + + jobs: + + build: + runs-on: ubuntu-latest + permissions: + contents: read + env: + dist_artifacts_name: dist + dist_artifacts_dir: dist + lock_file_artifact: uv.lock + steps: + - name: Setup | Checkout Repository at workflow sha + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + + - name: Setup | Force correct release branch on workflow sha + run: git checkout -B ${{ github.ref_name }} + + - name: Setup | Install uv + uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 # v4.0.2 + + - name: Setup | Install Python & Project dependencies + run: uv sync --extra build + + - name: Build | Build next version artifacts + id: version + env: + GH_TOKEN: "none" + run: uv run semantic-release -v version --no-commit --no-tag + + - name: Upload | Distribution Artifacts + if: ${{ steps.version.outputs.released == 'true' }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: ${{ env.dist_artifacts_name }} + path: ${{ format('{0}/**', env.dist_artifacts_dir) }} + if-no-files-found: error + retention-days: 2 + + - name: Upload | Lock File Artifact + if: ${{ steps.version.outputs.released == 'true' }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: ${{ env.lock_file_artifact }} + path: ${{ env.lock_file_artifact }} + if-no-files-found: error + retention-days: 2 + + outputs: + new-release-detected: ${{ steps.version.outputs.released }} + new-release-version: ${{ steps.version.outputs.version }} + new-release-tag: ${{ steps.version.outputs.tag }} + new-release-is-prerelease: ${{ steps.version.outputs.is_prerelease }} + distribution-artifacts: ${{ env.dist_artifacts_name }} + lock-file-artifact: ${{ env.lock_file_artifact }} + + + test-e2e: + needs: build + runs-on: ubuntu-latest + steps: + - name: Setup | Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + + - name: Setup | Download Distribution Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + if: ${{ needs.build.outputs.new-release-detected == 'true' }} + id: artifact-download + with: + name: ${{ needs.build.outputs.distribution-artifacts }} + path: ./dist + + - name: Setup | Install uv + uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 # v4.0.2 + + - name: Setup | Install Python & Project dependencies + run: uv sync --extra test + + - name: Setup | Install distribution artifact + if: ${{ steps.artifact-download.outcome == 'success' }} + run: | + uv pip uninstall my-package + uv pip install dist/python_semantic_release-*.whl + + - name: Test | Run pytest + run: uv run pytest -vv tests/e2e + + + release: + runs-on: ubuntu-latest + needs: + - build + - test-e2e + + if: ${{ needs.build.outputs.new-release-detected == 'true' }} + + concurrency: + group: ${{ github.workflow }}-release-${{ github.ref_name }} + cancel-in-progress: false + + permissions: + contents: write + + steps: + - name: Setup | Checkout Repository on Release Branch + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + + - name: Setup | Force release branch to be at workflow sha + run: git reset --hard ${{ github.sha }} + + - name: Setup | Install uv + uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 # v4.0.2 + + - name: Setup | Install Python & Project dependencies + run: uv sync --extra build + + - name: Setup | Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + id: artifact-download + with: + name: ${{ needs.build.outputs.distribution-artifacts }} + path: dist + + - name: Setup | Download Lock File Artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: ${{ needs.build.outputs.lock-file-artifact }} + + - name: Setup | Stage Lock File for Version Commit + run: git add uv.lock + + - name: Release | Create Release + id: release + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + bash .github/workflows/verify_upstream.sh + uv run semantic-release -v --strict version --skip-build + uv run semantic-release publish + + outputs: + released: ${{ steps.release.outputs.released }} + new-release-version: ${{ steps.release.outputs.version }} + new-release-tag: ${{ steps.release.outputs.tag }} + + + deploy: + name: Deploy + runs-on: ubuntu-latest + if: ${{ needs.release.outputs.released == 'true' && github.repository == 'python-semantic-release/my-package' }} + needs: + - build + - release + + environment: + name: pypi + url: https://pypi.org/project/my-package/ + + permissions: + id-token: write + + steps: + - name: Setup | Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + id: artifact-download + with: + name: ${{ needs.build.outputs.distribution-artifacts }} + path: dist + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 + with: + packages-dir: dist + print-hash: true + verbose: true diff --git a/docs/configuration/index.rst b/docs/configuration/index.rst index 3b5dade61..1044ae10d 100644 --- a/docs/configuration/index.rst +++ b/docs/configuration/index.rst @@ -3,16 +3,22 @@ Configuration ============= -Python Semantic Release is highly configurable, allowing you to tailor it to your project's needs. It supports -various runtime environments and can be integrated with different CI/CD services. +Python Semantic Release is highly configurable, allowing you to tailor it to your +project's needs. It supports various runtime environments and can be integrated with +different CI/CD services. -1. Check out the :ref:`Configuration Options ` to customize your release process. +#. Check out our set of :ref:`configuration guides ` to help walk + you through the set up of common project customizations. -2. Configure your :ref:`CI/CD services ` to use Python Semantic Release. +#. Dive in deep and explore the full set of possible :ref:`customization options `. + +#. Go Automatic and Configure your :ref:`CI/CD services ` to use Python + Semantic Release. .. toctree:: :maxdepth: 1 :hidden: - Configuration Options + Guides + Options automatic-releases/index diff --git a/src/semantic_release/cli/commands/version.py b/src/semantic_release/cli/commands/version.py index 8a8cf94a1..d3aed80f3 100644 --- a/src/semantic_release/cli/commands/version.py +++ b/src/semantic_release/cli/commands/version.py @@ -142,14 +142,10 @@ def apply_version_to_source_files( if not noop: logger.debug("Updating version %s in repository files...", version) - paths = list( - map( - lambda decl, new_version=version, noop=noop: ( # type: ignore[misc] - decl.update_file_w_version(new_version=new_version, noop=noop) - ), - version_declarations, - ) - ) + paths = [ + decl.update_file_w_version(new_version=version, noop=noop) + for decl in version_declarations + ] repo_filepaths = [ str(updated_file.relative_to(repo_dir)) @@ -650,10 +646,9 @@ def version( # noqa: C901 credential_masker=runtime.masker, ) - # Preparing for committing changes + # Preparing for committing changes; we always stage files even if we're not committing them in order to support a two-stage commit + project.git_add(paths=all_paths_to_add, noop=opts.noop) if commit_changes: - project.git_add(paths=all_paths_to_add, noop=opts.noop) - # NOTE: If we haven't modified any source code then we skip trying to make a commit # and any tag that we apply will be to the HEAD commit (made outside of # running PSR diff --git a/tests/e2e/cmd_version/test_version_stamp.py b/tests/e2e/cmd_version/test_version_stamp.py index a12059f37..182e9406b 100644 --- a/tests/e2e/cmd_version/test_version_stamp.py +++ b/tests/e2e/cmd_version/test_version_stamp.py @@ -3,7 +3,7 @@ import json from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import pytest import tomlkit @@ -61,8 +61,6 @@ def test_version_only_stamp_version( post_mocker: MagicMock, example_pyproject_toml: Path, example_project_dir: ExProjectDir, - example_changelog_md: Path, - example_changelog_rst: Path, ) -> None: repo = repo_result["repo"] version_file = example_project_dir.joinpath( @@ -97,10 +95,11 @@ def test_version_only_stamp_version( head_after = repo.head.commit tags_after = {tag.name for tag in repo.tags} tags_set_difference = set.difference(tags_after, tags_before) - differing_files = [ + actual_staged_files = [ # Make sure filepath uses os specific path separators str(Path(file)) - for file in str(repo.git.diff(name_only=True)).splitlines() + # Changed files should always be staged + for file in cast("str", repo.git.diff(staged=True, name_only=True)).splitlines() ] pyproject_toml_after = tomlkit.loads( example_pyproject_toml.read_text(encoding="utf-8") @@ -125,7 +124,7 @@ def test_version_only_stamp_version( assert post_mocker.call_count == 0 # no vcs release creation occurred # Files that should receive version change - assert expected_changed_files == differing_files + assert expected_changed_files == actual_staged_files # Compare pyproject.toml assert pyproject_toml_before == pyproject_toml_after From f9e152fb36cd2e590fe8c2bf85bbff08f7fc1c52 Mon Sep 17 00:00:00 2001 From: semantic-release Date: Thu, 12 Jun 2025 04:19:36 +0000 Subject: [PATCH 13/13] 10.1.0 Automatically generated by python-semantic-release --- CHANGELOG.rst | 35 +++++++++++++++++++ .../automatic-releases/github-actions.rst | 14 ++++---- pyproject.toml | 2 +- src/gh_action/requirements.txt | 2 +- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 09d90b45c..f7255ca13 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,41 @@ CHANGELOG ========= +.. _changelog-v10.1.0: + +v10.1.0 (2025-06-12) +==================== + +โœจ Features +----------- + +* **cmd-version**: Always stage version stamped files & changelog even with ``--no-commit``, closes + `#1211`_ (`PR#1214`_, `de62334`_) + +๐Ÿ“– Documentation +---------------- + +* **cmd-version**: Improve command description & include common uses (`PR#1214`_, `de62334`_) + +* **configuration-guide**: Add how-to guide for ``uv`` integration (`PR#1214`_, `de62334`_) + +* **github-actions**: Clarify with examples of the ``root_options`` v10 migration change + (`PR#1271`_, `fbb63ec`_) + +โš™๏ธ Build System +---------------- + +* **deps**: Expand ``python-gitlab`` dependency to include ``v6.0.0`` (`PR#1273`_, `99fc9cc`_) + +.. _#1211: https://github.com/python-semantic-release/python-semantic-release/issues/1211 +.. _99fc9cc: https://github.com/python-semantic-release/python-semantic-release/commit/99fc9ccabbae9adf5646731591080366eacbe03c +.. _de62334: https://github.com/python-semantic-release/python-semantic-release/commit/de623344cd18b3dbe05823eb90fdd010c5505c92 +.. _fbb63ec: https://github.com/python-semantic-release/python-semantic-release/commit/fbb63ec76142ea903d8a0401369ec251abbec0fe +.. _PR#1214: https://github.com/python-semantic-release/python-semantic-release/pull/1214 +.. _PR#1271: https://github.com/python-semantic-release/python-semantic-release/pull/1271 +.. _PR#1273: https://github.com/python-semantic-release/python-semantic-release/pull/1273 + + .. _changelog-v10.0.2: v10.0.2 (2025-05-26) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index 93024e3dd..5d81ea7d0 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -873,14 +873,14 @@ to the GitHub Release Assets as well. - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v10.0.2 + uses: python-semantic-release/python-semantic-release@v10.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.2 + uses: python-semantic-release/publish-action@v10.1.0 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -979,7 +979,7 @@ The equivalent GitHub Action configuration would be: - name: Action | Semantic Version Release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v10.0.2 + uses: python-semantic-release/python-semantic-release@v10.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -1038,14 +1038,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v10.0.2 + uses: python-semantic-release/python-semantic-release@v10.1.0 with: directory: ${{ env.SUBMODULE_1_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release submodule 2 id: release-submod-2 - uses: python-semantic-release/python-semantic-release@v10.0.2 + uses: python-semantic-release/python-semantic-release@v10.1.0 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1057,7 +1057,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.2 + uses: python-semantic-release/publish-action@v10.1.0 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -1065,7 +1065,7 @@ Publish Action. tag: ${{ steps.release-submod-1.outputs.tag }} - name: Publish | Upload package 2 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.2 + uses: python-semantic-release/publish-action@v10.1.0 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/pyproject.toml b/pyproject.toml index 5d4f90314..541520dc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "10.0.2" +version = "10.1.0" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } diff --git a/src/gh_action/requirements.txt b/src/gh_action/requirements.txt index 835d01792..44b3d8407 100644 --- a/src/gh_action/requirements.txt +++ b/src/gh_action/requirements.txt @@ -1 +1 @@ -python-semantic-release == 10.0.2 +python-semantic-release == 10.1.0 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy