From c5d30424667ca7344efe39e7f84ecf231b462a26 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 10 Nov 2025 13:02:57 -0700 Subject: [PATCH 01/18] ci(deps): bump `python-semantic-release@v10.4.1` action to `v10.5.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 c7d1033c5..fceeb4713 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -148,7 +148,7 @@ jobs: - name: Release | Python Semantic Release id: release - uses: python-semantic-release/python-semantic-release@4d4cb0ab842247caea1963132c242c62aab1e4d5 # v10.4.1 + uses: python-semantic-release/python-semantic-release@02f2a5c74dbb6aa2989f10fc4af12cd8e6bf025f # v10.5.2 with: github_token: ${{ secrets.GITHUB_TOKEN }} verbosity: 1 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 6f447d461..ddb32cb1c 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@4d4cb0ab842247caea1963132c242c62aab1e4d5 # v10.4.1 + uses: python-semantic-release/python-semantic-release@02f2a5c74dbb6aa2989f10fc4af12cd8e6bf025f # v10.5.2 with: github_token: "" verbosity: 1 From c355bc2757a0eb61638d95fec5cb090fc0aa5e08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:58:49 +0000 Subject: [PATCH 02/18] ci(deps): bump `python-semantic-release/publish-action@v10.4.1` to `v10.5.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 fceeb4713..591e9636a 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -155,7 +155,7 @@ jobs: build: false - name: Release | Add distribution artifacts to GitHub Release Assets - uses: python-semantic-release/publish-action@ae6462adc12bd3d1738070d784b65b5189b955a9 # v10.4.1 + uses: python-semantic-release/publish-action@948bb8fccc5e8072f2c52464b45c76a8bb3878e6 # v10.5.2 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} From 9bb4ac9fd929c53e14d4d982ab628fbfc9bd6613 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 10 Nov 2025 13:20:01 -0700 Subject: [PATCH 03/18] ci(release): remove verify upstream job step as PSR handles it internally (#1372) --- .github/workflows/cicd.yml | 6 ----- .github/workflows/verify_upstream.sh | 33 ---------------------------- 2 files changed, 39 deletions(-) delete mode 100644 .github/workflows/verify_upstream.sh diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 591e9636a..863413a05 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -140,12 +140,6 @@ jobs: python -m scripts.bump_version_in_docs git add docs/* - - name: Evaluate | Verify upstream has NOT changed - # Last chance to abort before causing an error as another PR/push was applied to the upstream branch - # while this workflow was running. This is important because we are committing a version change - shell: bash - run: bash .github/workflows/verify_upstream.sh - - name: Release | Python Semantic Release id: release uses: python-semantic-release/python-semantic-release@02f2a5c74dbb6aa2989f10fc4af12cd8e6bf025f # v10.5.2 diff --git a/.github/workflows/verify_upstream.sh b/.github/workflows/verify_upstream.sh deleted file mode 100644 index 8444eba57..000000000 --- a/.github/workflows/verify_upstream.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -set -eu +o pipefail - -# Example output of `git status -sb`: -# ## master...origin/master [behind 1] -# M .github/workflows/verify_upstream.sh -UPSTREAM_BRANCH_NAME="$(git status -sb | head -n 1 | awk -F '\\.\\.\\.' '{print $2}' | cut -d ' ' -f1)" -printf '%s\n' "Upstream branch name: $UPSTREAM_BRANCH_NAME" - -set -o pipefail - -if [ -z "$UPSTREAM_BRANCH_NAME" ]; then - printf >&2 '%s\n' "::error::Unable to determine upstream branch name!" - exit 1 -fi - -git fetch "${UPSTREAM_BRANCH_NAME%%/*}" - -if ! UPSTREAM_SHA="$(git rev-parse "$UPSTREAM_BRANCH_NAME")"; then - printf >&2 '%s\n' "::error::Unable to determine upstream branch sha!" - exit 1 -fi - -HEAD_SHA="$(git rev-parse HEAD)" - -if [ "$HEAD_SHA" != "$UPSTREAM_SHA" ]; then - printf >&2 '%s\n' "[HEAD SHA] $HEAD_SHA != $UPSTREAM_SHA [UPSTREAM SHA]" - printf >&2 '%s\n' "::error::Upstream has changed, aborting release..." - exit 1 -fi - -printf '%s\n' "Verified upstream branch has not changed, continuing with release..." From 2dd8be7b1ae5f0504b5c4ac44e74608802e20066 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 10 Nov 2025 13:34:16 -0700 Subject: [PATCH 04/18] chore(release): switch to major and minor partial tags via PSR (#1368) * chore(release): set `add_partial_tags` to true for PSR releases * ci(release): remove job steps to create partial tags --- .github/workflows/cicd.yml | 22 ---------------------- pyproject.toml | 1 + 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 863413a05..28ff7f332 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -155,28 +155,6 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ steps.release.outputs.tag }} - - name: Release | Update Minor Release Tag Reference - if: steps.release.outputs.released == 'true' && steps.release.outputs.is_prerelease == 'false' - env: - FULL_VERSION_TAG: ${{ steps.release.outputs.tag }} - GIT_COMMITTER_NAME: ${{ env.GITHUB_ACTIONS_AUTHOR_NAME }} - GIT_COMMITTER_EMAIL: ${{ env.GITHUB_ACTIONS_AUTHOR_EMAIL }} - run: | - MINOR_VERSION_TAG="$(echo "$FULL_VERSION_TAG" | cut -d. -f1,2)" - git tag --force --annotate "$MINOR_VERSION_TAG" "${FULL_VERSION_TAG}^{}" -m "$MINOR_VERSION_TAG" - git push -u origin "$MINOR_VERSION_TAG" --force - - - name: Release | Update Major Release Tag Reference - if: steps.release.outputs.released == 'true' && steps.release.outputs.is_prerelease == 'false' - env: - FULL_VERSION_TAG: ${{ steps.release.outputs.tag }} - GIT_COMMITTER_NAME: ${{ env.GITHUB_ACTIONS_AUTHOR_NAME }} - GIT_COMMITTER_EMAIL: ${{ env.GITHUB_ACTIONS_AUTHOR_EMAIL }} - run: | - MAJOR_VERSION_TAG="$(echo "$FULL_VERSION_TAG" | cut -d. -f1)" - git tag --force --annotate "$MAJOR_VERSION_TAG" "${FULL_VERSION_TAG}^{}" -m "$MAJOR_VERSION_TAG" - git push -u origin "$MAJOR_VERSION_TAG" --force - outputs: released: ${{ steps.release.outputs.released || 'false' }} new-release-version: ${{ steps.release.outputs.version }} diff --git a/pyproject.toml b/pyproject.toml index 71c8e9a6c..0af0ef360 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -409,6 +409,7 @@ sections = { "tests" = ["tests"] } ignore_names = ["change_to_ex_proj_dir", "init_example_project"] [tool.semantic_release] +add_partial_tags = true logging_use_named_masks = true commit_parser = "conventional" commit_parser_options = { parse_squash_commits = true, ignore_merge_commits = true } From cbc93f1a5595b8401b788a47c2ceace792ab77ef Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 13 Dec 2025 10:20:44 -0700 Subject: [PATCH 05/18] ci(validate-workflow): make sure github action test job runs when `gha_src` files change --- .github/workflows/validate.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ddb32cb1c..85dd39cb9 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -86,7 +86,7 @@ jobs: build: name: Build runs-on: ubuntu-latest - if: ${{ inputs.build-files-changed == 'true' || inputs.src-files-changed == 'true' || inputs.test-files-changed == 'true' || inputs.ci-files-changed == 'true' }} + if: ${{ inputs.build-files-changed == 'true' || inputs.src-files-changed == 'true' || inputs.test-files-changed == 'true' || inputs.ci-files-changed == 'true' || inputs.gha-src-files-changed == 'true' || inputs.gha-test-files-changed == 'true' }} steps: - name: Setup | Checkout Repository at workflow sha @@ -397,7 +397,6 @@ jobs: needs: - build - - unit-test env: TEST_CONTAINER_TAG: psr-action:latest From d4f2ca8ab5203f9f1de77e311980a7051beda2dc Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 13 Dec 2025 10:30:19 -0700 Subject: [PATCH 06/18] ci(actions): add names and fetch definitions for unnamed checkouts --- .github/workflows/ci.yml | 13 ++++++++----- .github/workflows/cicd.yml | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e045630e..9c59e6b8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,16 +16,18 @@ concurrency: jobs: - commitlint: + lint-commits: # condition: Execute IFF it is protected branch update, or a PR that is NOT in a draft state if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Setup | Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 - - uses: wagoid/commitlint-github-action@b948419dd99f3fd78a6548d48f94e3df7f6bf3ed # v6.2.1 + - name: Lint | Commit Messages + uses: wagoid/commitlint-github-action@b948419dd99f3fd78a6548d48f94e3df7f6bf3ed # v6.2.1 eval-changes: @@ -35,9 +37,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Setup | Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - fetch-depth: 100 + fetch-depth: 0 - name: Evaluate | Check common file types for changes id: core-changed-files diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 28ff7f332..a400db658 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Setup | Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - fetch-depth: 100 # Must at least retrieve a set of commits to compare changes - # primarily because of any 'Rebase and Merge' PR action in GitHub + fetch-depth: 0 - name: Evaluate | Check common file types for changes id: core-changed-files From a67b48e538def8c24265ac6ebd78580665b8db1c Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 13 Dec 2025 13:05:06 -0700 Subject: [PATCH 07/18] ci(deps): bump `actions/checkout@v5.0.0` to `v6.0.1` (#1394) --- .github/workflows/ci.yml | 4 ++-- .github/workflows/cicd.yml | 4 ++-- .github/workflows/validate.yml | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c59e6b8d..90ea7b174 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup | Checkout Repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 @@ -38,7 +38,7 @@ jobs: steps: - name: Setup | Checkout Repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index a400db658..29d792702 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Setup | Checkout Repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 @@ -115,7 +115,7 @@ jobs: # possible that the branch was updated while the workflow was running. This # prevents accidentally releasing un-evaluated changes. - name: Setup | Checkout Repository on Release Branch - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.ref_name }} fetch-depth: 0 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 85dd39cb9..55ecf8740 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -90,7 +90,7 @@ jobs: steps: - name: Setup | Checkout Repository at workflow sha - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.sha }} fetch-depth: 0 @@ -161,7 +161,7 @@ jobs: steps: - name: Setup | Checkout Repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.sha }} fetch-depth: 1 @@ -217,7 +217,7 @@ jobs: steps: - name: Setup | Checkout Repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.sha }} fetch-depth: 1 @@ -306,7 +306,7 @@ jobs: steps: - name: Setup | Checkout Repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.sha }} fetch-depth: 1 @@ -404,7 +404,7 @@ jobs: steps: - name: Setup | Checkout Repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 1 ref: ${{ github.sha }} @@ -446,7 +446,7 @@ jobs: steps: - name: Setup | Checkout Repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.sha }} fetch-depth: 1 From 9d5f246c437ab57aa645d02a6a4167a17ba7de84 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 13 Dec 2025 13:14:39 -0700 Subject: [PATCH 08/18] ci(deps): bump `actions/download-artifact@v6.0.0` to `v7.0.0` (#1394) --- .github/workflows/cicd.yml | 4 ++-- .github/workflows/validate.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 29d792702..bd833d120 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -125,7 +125,7 @@ jobs: git reset --hard ${{ github.sha }} - name: Setup | Download Build Artifacts - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 id: artifact-download with: name: ${{ needs.validate.outputs.distribution-artifacts }} @@ -179,7 +179,7 @@ jobs: steps: - name: Setup | Download Build Artifacts - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 id: artifact-download with: name: ${{ needs.validate.outputs.distribution-artifacts }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 55ecf8740..dfc87efbb 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -229,7 +229,7 @@ jobs: cache: 'pip' - name: Setup | Download Distribution Artifacts - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: ${{ needs.build.outputs.distribution-artifacts }} path: ./dist @@ -318,7 +318,7 @@ jobs: cache: 'pip' - name: Setup | Download Distribution Artifacts - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: ${{ needs.build.outputs.distribution-artifacts }} path: dist @@ -410,7 +410,7 @@ jobs: ref: ${{ github.sha }} - name: Setup | Download Distribution Artifacts - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: ${{ needs.build.outputs.distribution-artifacts }} path: ${{ env.ACTION_SRC_DIR }} From b984f41d62522f11994b99cd4743ac052e87599e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Dec 2025 17:55:57 +0000 Subject: [PATCH 09/18] ci(deps): bump `actions/upload-artifact@v5.0.0` to `v6.0.0` (#1394) --- .github/workflows/validate.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index dfc87efbb..41c83ca92 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -139,7 +139,7 @@ jobs: printf '%s\n' "artifacts_name=dist" >> $GITHUB_OUTPUT - name: Upload | Distribution Artifacts - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ steps.build.outputs.artifacts_name }} path: ${{ steps.build.outputs.dist_dir }} @@ -265,7 +265,7 @@ jobs: --junit-xml=tests/reports/pytest-results.xml - name: Report | Upload Cached Repos on Failure - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: ${{ failure() && steps.tests.outcome == 'failure' }} with: name: ${{ format('cached-repos-{0}-{1}', matrix.os, matrix.python-version) }} @@ -275,7 +275,7 @@ jobs: retention-days: 1 - name: Report | Upload Tested Repos on Failure - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: ${{ failure() && steps.tests.outcome == 'failure' }} with: name: ${{ format('tested-repos-{0}-{1}', matrix.os, matrix.python-version) }} @@ -363,7 +363,7 @@ jobs: `--junit-xml=tests/reports/pytest-results.xml - name: Report | Upload Cached Repos on Failure - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: ${{ failure() && steps.tests.outcome == 'failure' }} with: name: ${{ format('cached-repos-{0}-{1}', matrix.os, matrix.python-version) }} @@ -373,7 +373,7 @@ jobs: retention-days: 1 - name: Report | Upload Tested Repos on Failure - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: ${{ failure() && steps.tests.outcome == 'failure' }} with: name: ${{ format('tested-repos-{0}-{1}', matrix.os, matrix.python-version) }} From e0927e30fd1b356f74746c3af604d74851cc7b7a Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 13 Dec 2025 13:23:32 -0700 Subject: [PATCH 10/18] ci(deps): bump `actions/setup-python@v6.0.0` to `v6.1.0` (#1394) --- .github/workflows/manual.yml | 2 +- .github/workflows/validate.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index 4f9e277c4..67d256fea 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -65,7 +65,7 @@ jobs: steps: - name: Setup | Install Python ${{ env.COMMON_PYTHON_VERSION }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.COMMON_PYTHON_VERSION }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 41c83ca92..0e1e7654c 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -100,7 +100,7 @@ jobs: git checkout -B ${{ github.ref_name }} - name: Setup | Install Python ${{ env.COMMON_PYTHON_VERSION }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.COMMON_PYTHON_VERSION }} cache: 'pip' @@ -167,7 +167,7 @@ jobs: fetch-depth: 1 - name: Setup | Install Python ${{ env.LOWEST_PYTHON_VERSION }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.LOWEST_PYTHON_VERSION }} cache: 'pip' @@ -223,7 +223,7 @@ jobs: fetch-depth: 1 - name: Setup | Install Python ${{ matrix.python-version }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ matrix.python-version }} cache: 'pip' @@ -312,7 +312,7 @@ jobs: fetch-depth: 1 - name: Setup | Install Python ${{ matrix.python-version }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ matrix.python-version }} cache: 'pip' @@ -452,7 +452,7 @@ jobs: fetch-depth: 1 - name: Setup | Install Python ${{ env.COMMON_PYTHON_VERSION }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.COMMON_PYTHON_VERSION }} cache: 'pip' From edf022986be56b87b09e496eaf747285c951d6e2 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 13 Dec 2025 13:27:29 -0700 Subject: [PATCH 11/18] ci(deps): bump `actions/stale@v10.1.0` to `v10.1.1` (#1394) --- .github/workflows/stale.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 69f838a67..ba5c00d1c 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Stale Issues/PRs - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: # default: 30, GitHub Actions API Rate limit is 1000/hr operations-per-run: ${{ env.OPERATIONS_RATE_LIMIT }} @@ -67,7 +67,7 @@ jobs: # that point the submitter has 14 days before a reminder/warning is given. If # no response has been received within 3 weeks, the issue is closed. There are # no exemptions besides removing the awaiting-reply label. - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: # GitHub Actions API Rate limit is 1000/hr operations-per-run: ${{ env.OPERATIONS_RATE_LIMIT }} @@ -97,7 +97,7 @@ jobs: # forgotten completely, this job will post a reminder message to the maintainers # No closures will occur and there are no exemptions besides removing the confirmed # label. - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: # GitHub Actions API Rate limit is 1000/hr operations-per-run: ${{ env.OPERATIONS_RATE_LIMIT }} From 779af882f071254324d9867142e5299979e53fab Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 13 Dec 2025 13:12:02 -0700 Subject: [PATCH 12/18] ci(deps): bump `tj-actions/changed-files@v47.0.0` action to `v47.0.1` (#1394) --- .github/workflows/ci.yml | 4 ++-- .github/workflows/cicd.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90ea7b174..1d7930dfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,13 +44,13 @@ jobs: - name: Evaluate | Check common file types for changes id: core-changed-files - uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 #v47.0.0 + uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 #v47.0.1 with: files_yaml_from_source_file: .github/changed-files-spec.yml - name: Evaluate | Check specific file types for changes id: ci-changed-files - uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 #v47.0.0 + uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 #v47.0.1 with: files_yaml: | ci: diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index bd833d120..b1696d9b6 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -26,14 +26,14 @@ jobs: - name: Evaluate | Check common file types for changes id: core-changed-files - uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 #v47.0.0 + uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 #v47.0.1 with: base_sha: ${{ github.event.push.before }} files_yaml_from_source_file: .github/changed-files-spec.yml - name: Evaluate | Check specific file types for changes id: ci-changed-files - uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 #v47.0.0 + uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 #v47.0.1 with: base_sha: ${{ github.event.push.before }} files_yaml: | From 18b7edadd7e7dfe42ec43110acf5e1bd8bcd7eb3 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 10:13:06 -0800 Subject: [PATCH 13/18] fix(github-action): fix failed signing issue when ssh was missing from action environment (#1389) Install openssh-client in the slim container image Resolves: #1376 * test(gh-action): add SSH signing test case Add test to verify SSH signing key configuration in the GitHub Action. The test generates an SSH key pair and validates that ssh-agent and ssh-add commands execute successfully when SSH signing keys are provided. --- src/gh_action/Dockerfile | 2 + .../suite/test_version_ssh_signing.sh | 105 ++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 tests/gh_action/suite/test_version_ssh_signing.sh diff --git a/src/gh_action/Dockerfile b/src/gh_action/Dockerfile index 7166042ab..7ccbd3d40 100644 --- a/src/gh_action/Dockerfile +++ b/src/gh_action/Dockerfile @@ -16,6 +16,8 @@ RUN \ apt update && apt install -y --no-install-recommends \ # install git with git-lfs support git git-lfs \ + # install ssh client for git signing + openssh-client \ # install python cmodule / binary module build utilities python3-dev gcc make cmake cargo \ # Configure global pip diff --git a/tests/gh_action/suite/test_version_ssh_signing.sh b/tests/gh_action/suite/test_version_ssh_signing.sh new file mode 100644 index 000000000..0f0c82c1a --- /dev/null +++ b/tests/gh_action/suite/test_version_ssh_signing.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +__file__="$(realpath "${BASH_SOURCE[0]}")" +__directory__="$(dirname "${__file__}")" + +if ! [ "${UTILS_LOADED}" = "true" ]; then + # shellcheck source=tests/utils.sh + source "$__directory__/../utils.sh" +fi + +test_version_ssh_signing() { + # Test that SSH signing keys are correctly configured in the action + # We will generate an SSH key pair and pass it to the action to ensure + # the ssh-agent and ssh-add commands work correctly + local index="${1:?Index not provided}" + local test_name="${FUNCNAME[0]}" + + # Generate a temporary SSH key pair for testing + local ssh_key_dir + ssh_key_dir="$(mktemp -d)" + local ssh_private_key_file="$ssh_key_dir/signing_key" + local ssh_public_key_file="$ssh_key_dir/signing_key.pub" + + # Generate SSH key pair (Ed25519 for faster generation and smaller keys) + # Note: Using empty passphrase (-N "") for test purposes only + if ! ssh-keygen -t ed25519 -N "" -f "$ssh_private_key_file" -C "test@example.com" >/dev/null 2>&1; then + error "Failed to generate SSH key pair!" + rm -rf "$ssh_key_dir" + return 1 + fi + + # Read the generated keys + local ssh_public_key + local ssh_private_key + ssh_public_key="$(cat "$ssh_public_key_file")" + ssh_private_key="$(cat "$ssh_private_key_file")" + + # Clean up the temporary key files + rm -rf "$ssh_key_dir" + + # Create expectations & set env variables that will be passed in for Docker command + local WITH_VAR_GITHUB_TOKEN="ghp_1x2x3x4x5x6x7x8x9x0x1x2x3x4x5x6x7x8x9x0" + local WITH_VAR_NO_OPERATION_MODE="true" + local WITH_VAR_VERBOSITY="2" + local WITH_VAR_GIT_COMMITTER_NAME="Test User" + local WITH_VAR_GIT_COMMITTER_EMAIL="test@example.com" + local WITH_VAR_SSH_PUBLIC_SIGNING_KEY="$ssh_public_key" + local WITH_VAR_SSH_PRIVATE_SIGNING_KEY="$ssh_private_key" + + # Expected messages in output + local expected_ssh_setup_msg="SSH Key pair found, configuring signing..." + local expected_psr_cmd=".*/bin/semantic-release -vv --noop version" + + # Execute the test & capture output + local output="" + if ! output="$(run_test "$index. $test_name" 2>&1)"; then + # Log the output for debugging purposes + log "$output" + error "fatal error occurred!" + error "::error:: $test_name failed!" + return 1 + fi + + # Evaluate the output to ensure SSH setup message is present + if ! printf '%s' "$output" | grep -q "$expected_ssh_setup_msg"; then + # Log the output for debugging purposes + log "$output" + error "Failed to find SSH setup message in the output!" + error "\tExpected Message: $expected_ssh_setup_msg" + error "::error:: $test_name failed!" + return 1 + fi + + # Evaluate the output to ensure ssh-agent was started successfully + if ! printf '%s' "$output" | grep -q "Agent pid"; then + # Log the output for debugging purposes + log "$output" + error "Failed to find ssh-agent start message in the output!" + error "\tExpected Message pattern: 'Agent pid'" + error "::error:: $test_name failed!" + return 1 + fi + + # Evaluate the output to ensure ssh-add was successful + if ! printf '%s' "$output" | grep -q "Identity added"; then + # Log the output for debugging purposes + log "$output" + error "Failed to find ssh-add success message in the output!" + error "\tExpected Message pattern: 'Identity added'" + error "::error:: $test_name failed!" + return 1 + fi + + # Evaluate the output to ensure the expected command is present + if ! printf '%s' "$output" | grep -q -E "$expected_psr_cmd"; then + # Log the output for debugging purposes + log "$output" + error "Failed to find the expected command in the output!" + error "\tExpected Command: $expected_psr_cmd" + error "::error:: $test_name failed!" + return 1 + fi + + log "\n$index. $test_name: PASSED!" +} From e164f682bfa4ca1e7cbe77aa068202fd8094eec7 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 10:43:58 -0800 Subject: [PATCH 14/18] fix(cmd-version): resolve unauthenticated git repo issues for upstream verification (#1388) This change updates verify_upstream_unchanged to accept and use an authenticated remote_url parameter when fetching from the remote, mirroring the approach used for git push operations. This resolves authentication issues when verifying upstream state in repositories that require token authentication for fetch operations. Resolves: #1373 * test(gitproject): add another unit test for verify upstream unchanged with authed url --- src/semantic_release/cli/commands/version.py | 1 + src/semantic_release/gitproject.py | 48 ++++++++++++++++++- .../unit/semantic_release/test_gitproject.py | 20 ++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/semantic_release/cli/commands/version.py b/src/semantic_release/cli/commands/version.py index d96e0d8ab..7a6fa26ef 100644 --- a/src/semantic_release/cli/commands/version.py +++ b/src/semantic_release/cli/commands/version.py @@ -753,6 +753,7 @@ def version( # noqa: C901 project.verify_upstream_unchanged( local_ref="HEAD~1", upstream_ref=config.remote.name, + remote_url=remote_url, noop=opts.noop, ) except UpstreamBranchChangedError as exc: diff --git a/src/semantic_release/gitproject.py b/src/semantic_release/gitproject.py index a5e4e4e19..cc86d33b5 100644 --- a/src/semantic_release/gitproject.py +++ b/src/semantic_release/gitproject.py @@ -336,13 +336,18 @@ def git_push_tag( raise GitPushError(f"Failed to push tag ({tag}) to remote") from err def verify_upstream_unchanged( # noqa: C901 - self, local_ref: str = "HEAD", upstream_ref: str = "origin", noop: bool = False + self, + local_ref: str = "HEAD", + upstream_ref: str = "origin", + remote_url: str | None = None, + noop: bool = False, ) -> None: """ Verify that the upstream branch has not changed since the given local reference. :param local_ref: The local reference to compare against upstream (default: HEAD) :param upstream_ref: The name of the upstream remote or specific remote branch (default: origin) + :param remote_url: Optional authenticated remote URL to use for fetching (default: None, uses configured remote) :param noop: Whether to skip the actual verification (for dry-run mode) :raises UpstreamBranchChangedError: If the upstream branch has changed @@ -409,7 +414,46 @@ def verify_upstream_unchanged( # noqa: C901 # Fetch the latest changes from the remote self.logger.info("Fetching latest changes from remote '%s'", remote_name) try: - remote_ref_obj.fetch() + # Check if we should use authenticated URL for fetch + # Only use remote_url if: + # 1. It's provided and different from the configured remote URL + # 2. It contains authentication credentials (@ symbol) + # 3. The configured remote is NOT a local path, file:// URL, or test URL (example.com) + # This ensures we don't break tests or local development + configured_url = remote_ref_obj.url + is_local_or_test_remote = ( + configured_url.startswith(("file://", "/", "C:/", "H:/")) + or "example.com" in configured_url + or not configured_url.startswith( + ( + "https://", + "http://", + "git://", + "git@", + "ssh://", + "git+ssh://", + ) + ) + ) + + use_authenticated_fetch = ( + remote_url + and "@" in remote_url + and remote_url != configured_url + and not is_local_or_test_remote + ) + + if use_authenticated_fetch: + # Use authenticated remote URL for fetch + # Fetch the remote branch and update the local tracking ref + repo.git.fetch( + remote_url, + f"refs/heads/{remote_branch_name}:refs/remotes/{upstream_full_ref_name}", + ) + else: + # Use the default remote configuration for local paths, + # file:// URLs, test URLs, or when no authentication is needed + remote_ref_obj.fetch() except GitCommandError as err: self.logger.exception(str(err)) err_msg = f"Failed to fetch from remote '{remote_name}'" diff --git a/tests/unit/semantic_release/test_gitproject.py b/tests/unit/semantic_release/test_gitproject.py index 09193d317..58bfedf81 100644 --- a/tests/unit/semantic_release/test_gitproject.py +++ b/tests/unit/semantic_release/test_gitproject.py @@ -62,6 +62,7 @@ def mock_repo(tmp_path: Path) -> RepoMock: # Mock remotes remote_obj = MagicMock() remote_obj.fetch = MagicMock() + remote_obj.url = "https://github.com/owner/repo.git" # Set a non-test URL # Mock refs for the remote ref_obj = MagicMock() @@ -249,6 +250,25 @@ def test_verify_upstream_unchanged_with_custom_ref( mock_repo.git.rev_parse.assert_called_once_with("HEAD~1") +def test_verify_upstream_unchanged_with_remote_url( + mock_gitproject: GitProject, mock_repo: RepoMock +): + """Test that verify_upstream_unchanged uses remote_url when provided.""" + remote_url = "https://token:x-oauth-basic@github.com/owner/repo.git" + + # Should not raise an exception + mock_gitproject.verify_upstream_unchanged( + local_ref="HEAD", remote_url=remote_url, noop=False + ) + + # Verify git.fetch was called with the remote_url and proper refspec instead of remote_ref_obj.fetch() + mock_repo.git.fetch.assert_called_once_with( + remote_url, "refs/heads/main:refs/remotes/origin/main" + ) + # Verify that remote_ref_obj.fetch() was NOT called + mock_repo.remotes["origin"].fetch.assert_not_called() + + def test_is_shallow_clone_true(mock_gitproject: GitProject, tmp_path: Path) -> None: """Test is_shallow_clone returns True when shallow file exists.""" # Create a shallow file From 27a006c59b84b9563cb50cfbd78d7284a67f6750 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 14 Dec 2025 13:36:39 -0700 Subject: [PATCH 15/18] test(fixtures): upgrade commit simulation to modify specific files --- tests/fixtures/git_repo.py | 52 ++++++++++++-------------------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/tests/fixtures/git_repo.py b/tests/fixtures/git_repo.py index 46ebed0d6..613955290 100644 --- a/tests/fixtures/git_repo.py +++ b/tests/fixtures/git_repo.py @@ -142,6 +142,7 @@ class CommitDef(TypedDict): sha: str datetime: NotRequired[DatetimeISOStr] include_in_changelog: bool + file_to_change: NotRequired[Path | str] class BaseRepoVersionDef(TypedDict): """A Common Repo definition for a get_commits_repo_*() fixture with all commit convention types""" @@ -290,6 +291,7 @@ class CommitSpec(TypedDict): scipy: str datetime: NotRequired[DatetimeISOStr] include_in_changelog: NotRequired[bool] + file_to_change: NotRequired[Path | str] class DetailsBase(TypedDict): pre_actions: NotRequired[Sequence[RepoActions]] @@ -1127,7 +1129,9 @@ def _simulate_change_commits_n_rtn_changelog_entry( changelog_entries: list[CommitDef] = [] for commit_msg in commit_msgs: if not git_repo.is_dirty(index=True, working_tree=False): - add_text_to_file(git_repo, file_in_repo) + add_text_to_file( + git_repo, str(commit_msg.get("file_to_change", file_in_repo)) + ) changelog_entries.append(commit_n_rtn_changelog_entry(git_repo, commit_msg)) @@ -1336,39 +1340,6 @@ def _configure_base_repo( # noqa: C901 @pytest.fixture(scope="session") def separate_squashed_commit_def() -> SeparateSquashedCommitDefFn: - # default_conventional_parser: ConventionalCommitParser, - # default_emoji_parser: EmojiCommitParser, - # default_scipy_parser: ScipyCommitParser, - # message_parsers: dict[ - # CommitConvention, - # ConventionalCommitParser | EmojiCommitParser | ScipyCommitParser, - # ] = { - # "conventional": ConventionalCommitParser( - # options=ConventionalCommitParserOptions( - # **{ - # **default_conventional_parser.options.__dict__, - # "parse_squash_commits": True, - # } - # ) - # ), - # "emoji": EmojiCommitParser( - # options=EmojiParserOptions( - # **{ - # **default_emoji_parser.options.__dict__, - # "parse_squash_commits": True, - # } - # ) - # ), - # "scipy": ScipyCommitParser( - # options=ScipyParserOptions( - # **{ - # **default_scipy_parser.options.__dict__, - # "parse_squash_commits": True, - # } - # ) - # ), - # } - def _separate_squashed_commit_def( squashed_commit_def: CommitDef, parser: SquashedCommitSupportedParser, @@ -1435,7 +1406,7 @@ def _convert( ) # Extract the correct commit message for the commit type - return { + commit_def: CommitDef = { **parse_msg_fn(commit_spec[commit_type], parser=parser), "cid": commit_spec["cid"], "datetime": ( @@ -1443,9 +1414,18 @@ def _convert( if "datetime" in commit_spec else stable_now_date.isoformat(timespec="seconds") ), - "include_in_changelog": (commit_spec.get("include_in_changelog", True)), + "include_in_changelog": commit_spec.get("include_in_changelog", True), } + if "file_to_change" in commit_spec: + commit_def.update( + { + "file_to_change": commit_spec["file_to_change"], + } + ) + + return commit_def + return _convert From 427af480235d3d0e26dabd8a0ff5133586772f37 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 14 Dec 2025 13:37:18 -0700 Subject: [PATCH 16/18] test(fixtures): upgrade monorepos to monitor external package files --- tests/fixtures/monorepos/example_monorepo.py | 44 ++++++++++++++++++- .../github_flow/monorepo_w_default_release.py | 29 ++++++++---- .../monorepo_w_release_channels.py | 25 ++++++----- .../trunk_based_dev/monorepo_w_tags.py | 30 ++++++++++--- 4 files changed, 101 insertions(+), 27 deletions(-) diff --git a/tests/fixtures/monorepos/example_monorepo.py b/tests/fixtures/monorepos/example_monorepo.py index 6150e0635..1b46bde2b 100644 --- a/tests/fixtures/monorepos/example_monorepo.py +++ b/tests/fixtures/monorepos/example_monorepo.py @@ -68,8 +68,12 @@ def build_spec_hash_4_example_monorepo( @pytest.fixture(scope="session") def cached_example_monorepo( build_repo_or_copy_cache: BuildRepoOrCopyCacheFn, - monorepo_pkg1_dir: Path, - monorepo_pkg2_dir: Path, + monorepo_pkg1_name: str, + monorepo_pkg2_name: str, + monorepo_pkg1_dir: str, + monorepo_pkg2_dir: str, + monorepo_pkg1_docs_dir: str, + monorepo_pkg2_docs_dir: str, monorepo_pkg1_version_py_file: Path, monorepo_pkg2_version_py_file: Path, monorepo_pkg1_pyproject_toml_file: Path, @@ -104,6 +108,13 @@ def hello_world() -> None: print("{pkg_name} Hello World") ''' ).lstrip() + doc_index_contents = dedent( + """ + ================== + {pkg_name} Documentation + ================== + """ + ).lstrip() with temporary_working_directory(cached_project_path): update_version_py_file( @@ -127,6 +138,14 @@ def hello_world() -> None: (".gitignore", gitignore_contents), (monorepo_pkg1_pyproject_toml_file, EXAMPLE_PYPROJECT_TOML_CONTENT), (monorepo_pkg2_pyproject_toml_file, EXAMPLE_PYPROJECT_TOML_CONTENT), + ( + Path(monorepo_pkg1_docs_dir, "index.rst"), + doc_index_contents.format(pkg_name=monorepo_pkg1_name), + ), + ( + Path(monorepo_pkg2_docs_dir, "index.rst"), + doc_index_contents.format(pkg_name=monorepo_pkg2_name), + ), ] for file, contents in file_2_contents: @@ -216,6 +235,27 @@ def monorepo_pkg2_name() -> str: return "pkg2" +@pytest.fixture(scope="session") +def monorepo_pkg_docs_dir_pattern() -> str: + return str(Path("docs", "source", "{package_name}")) + + +@pytest.fixture(scope="session") +def monorepo_pkg1_docs_dir( + monorepo_pkg1_name: str, + monorepo_pkg_docs_dir_pattern: str, +) -> str: + return monorepo_pkg_docs_dir_pattern.format(package_name=monorepo_pkg1_name) + + +@pytest.fixture(scope="session") +def monorepo_pkg2_docs_dir( + monorepo_pkg2_name: str, + monorepo_pkg_docs_dir_pattern: str, +) -> str: + return monorepo_pkg_docs_dir_pattern.format(package_name=monorepo_pkg2_name) + + @pytest.fixture(scope="session") def monorepo_pkg_dir_pattern() -> str: return str(Path("packages", "{package_name}")) diff --git a/tests/fixtures/monorepos/github_flow/monorepo_w_default_release.py b/tests/fixtures/monorepos/github_flow/monorepo_w_default_release.py index f42789953..ecbe491ed 100644 --- a/tests/fixtures/monorepos/github_flow/monorepo_w_default_release.py +++ b/tests/fixtures/monorepos/github_flow/monorepo_w_default_release.py @@ -96,8 +96,10 @@ def get_repo_definition_4_github_flow_monorepo_w_default_release_channel( monorepo_pkg2_changelog_rst_file: Path, monorepo_pkg1_name: str, monorepo_pkg2_name: str, - monorepo_pkg1_dir: Path, - monorepo_pkg2_dir: Path, + monorepo_pkg1_dir: str, + monorepo_pkg2_dir: str, + monorepo_pkg1_docs_dir: str, + monorepo_pkg2_docs_dir: str, monorepo_pkg1_version_py_file: Path, monorepo_pkg2_version_py_file: Path, monorepo_pkg1_pyproject_toml_file: Path, @@ -117,7 +119,8 @@ def get_repo_definition_4_github_flow_monorepo_w_default_release_channel( * chore(release): pkg1@1.1.0 [skip ci] (tag: pkg1-v1.1.0, branch: main, HEAD -> main) * feat(pkg1): file modified outside of pkg 1, identified by scope (#5) | - | * feat(pkg1): file modified outside of pkg 1, identified by scope (branch: pkg1/feat/pr-4) + | * docs: pkg1 docs modified outside of pkg 1, identified by path filter (branch: pkg1/feat/pr-4) + | * feat(pkg1): file modified outside of pkg 1, identified by scope |/ * chore(release): pkg2@1.1.1 [skip ci] (tag: pkg2-v1.1.1) * fix(pkg2-cli): file modified outside of pkg 2, identified by scope (#4) @@ -207,21 +210,23 @@ def _get_repo_from_definition( if commit_type != "conventional": raise ValueError(f"Unsupported commit type: {commit_type}") + pkg1_path_filters = (".", f"../../{monorepo_pkg1_docs_dir}") pkg1_commit_parser = ConventionalCommitMonorepoParser( options=ConventionalCommitMonorepoParserOptions( parse_squash_commits=True, ignore_merge_commits=ignore_merge_commits, scope_prefix=f"{monorepo_pkg1_name}-?", - path_filters=(".",), + path_filters=pkg1_path_filters, ) ) + pkg2_path_filters = (".", f"../../{monorepo_pkg2_docs_dir}") pkg2_commit_parser = ConventionalCommitMonorepoParser( options=ConventionalCommitMonorepoParserOptions( parse_squash_commits=pkg1_commit_parser.options.parse_squash_commits, ignore_merge_commits=pkg1_commit_parser.options.ignore_merge_commits, scope_prefix=f"{monorepo_pkg2_name}-?", - path_filters=(".",), + path_filters=pkg2_path_filters, ) ) @@ -277,7 +282,7 @@ def _get_repo_from_definition( ) ), "tool.semantic_release.commit_parser_options.scope_prefix": pkg1_commit_parser.options.scope_prefix, - "tool.semantic_release.commit_parser_options.path_filters": pkg1_commit_parser.options.path_filters, + "tool.semantic_release.commit_parser_options.path_filters": pkg1_path_filters, **(extra_configs or {}), }, }, @@ -301,7 +306,7 @@ def _get_repo_from_definition( ) ), "tool.semantic_release.commit_parser_options.scope_prefix": pkg2_commit_parser.options.scope_prefix, - "tool.semantic_release.commit_parser_options.path_filters": pkg2_commit_parser.options.path_filters, + "tool.semantic_release.commit_parser_options.path_filters": pkg2_path_filters, **(extra_configs or {}), }, }, @@ -774,7 +779,15 @@ def _get_repo_from_definition( "emoji": ":sparkles: (pkg1) file modified outside of pkg 1, identified by scope", "scipy": "ENH:pkg1: file modified outside of pkg 1, identified by scope", "datetime": next(commit_timestamp_gen), - } + }, + { + "cid": "pkg1-docs-2-squashed", + "conventional": "docs: pkg1 docs modified outside of pkg 1, identified by path filter", + "emoji": ":book: pkg1 docs modified outside of pkg 1, identified by path filter", + "scipy": "DOC: pkg1 docs modified outside of pkg 1, identified by path filter", + "datetime": next(commit_timestamp_gen), + "file_to_change": f"{monorepo_pkg1_docs_dir}/index.rst", + }, ] repo_construction_steps.extend( diff --git a/tests/fixtures/monorepos/github_flow/monorepo_w_release_channels.py b/tests/fixtures/monorepos/github_flow/monorepo_w_release_channels.py index 1e25cdb4f..9b4027195 100644 --- a/tests/fixtures/monorepos/github_flow/monorepo_w_release_channels.py +++ b/tests/fixtures/monorepos/github_flow/monorepo_w_release_channels.py @@ -97,8 +97,10 @@ def get_repo_definition_4_github_flow_monorepo_w_feature_release_channel( monorepo_pkg2_changelog_rst_file: Path, monorepo_pkg1_name: str, monorepo_pkg2_name: str, - monorepo_pkg1_dir: Path, - monorepo_pkg2_dir: Path, + monorepo_pkg1_dir: str, + monorepo_pkg2_dir: str, + monorepo_pkg1_docs_dir: str, + monorepo_pkg2_docs_dir: str, monorepo_pkg1_version_py_file: Path, monorepo_pkg2_version_py_file: Path, stable_now_date: GetStableDateNowFn, @@ -119,7 +121,7 @@ def get_repo_definition_4_github_flow_monorepo_w_feature_release_channel( | * chore(release): pkg2@1.1.0-alpha.2 [skip ci] (tag: pkg2-v1.1.0-alpha.2, branch: pkg2/feat/pr-2) | * fix(pkg2-cli): file modified outside of pkg 2, identified by scope | * chore(release): pkg2@1.1.0-alpha.1 [skip ci] (tag: pkg2-v1.1.0-alpha.1) - | * docs: add cli documentation + | * docs: pkg2 docs modified outside of pkg 2, identified by path filter | * test: add cli tests | * feat: no pkg scope but file in pkg 2 directory |/ @@ -203,21 +205,23 @@ def _get_repo_from_definition( if commit_type != "conventional": raise ValueError(f"Unsupported commit type: {commit_type}") + pkg1_path_filters = (".", f"../../{monorepo_pkg1_docs_dir}") pkg1_commit_parser = ConventionalCommitMonorepoParser( options=ConventionalCommitMonorepoParserOptions( parse_squash_commits=True, ignore_merge_commits=ignore_merge_commits, scope_prefix=f"{monorepo_pkg1_name}-?", - path_filters=(".",), + path_filters=pkg1_path_filters, ) ) + pkg2_path_filters = (".", f"../../{monorepo_pkg2_docs_dir}") pkg2_commit_parser = ConventionalCommitMonorepoParser( options=ConventionalCommitMonorepoParserOptions( parse_squash_commits=pkg1_commit_parser.options.parse_squash_commits, ignore_merge_commits=pkg1_commit_parser.options.ignore_merge_commits, scope_prefix=f"{monorepo_pkg2_name}-?", - path_filters=(".",), + path_filters=pkg2_path_filters, ) ) @@ -277,7 +281,7 @@ def _get_repo_from_definition( "prerelease_token": "alpha", }, "tool.semantic_release.commit_parser_options.scope_prefix": pkg1_commit_parser.options.scope_prefix, - "tool.semantic_release.commit_parser_options.path_filters": pkg1_commit_parser.options.path_filters, + "tool.semantic_release.commit_parser_options.path_filters": pkg1_path_filters, **(extra_configs or {}), }, }, @@ -307,7 +311,7 @@ def _get_repo_from_definition( "prerelease_token": "alpha", }, "tool.semantic_release.commit_parser_options.scope_prefix": pkg2_commit_parser.options.scope_prefix, - "tool.semantic_release.commit_parser_options.path_filters": pkg2_commit_parser.options.path_filters, + "tool.semantic_release.commit_parser_options.path_filters": pkg2_path_filters, **(extra_configs or {}), }, }, @@ -646,11 +650,12 @@ def _get_repo_from_definition( cid_pkg2_feb1_c3_docs := "pkg2_feat_branch_1_c3_docs" ), - "conventional": "docs: add cli documentation", - "emoji": ":memo: add cli documentation", - "scipy": "DOC: add cli documentation", + "conventional": "docs: pkg2 docs modified outside of pkg 2, identified by path filter", + "emoji": ":book: pkg2 docs modified outside of pkg 2, identified by path filter", + "scipy": "DOC: pkg2 docs modified outside of pkg 2, identified by path filter", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, + "file_to_change": f"../../{monorepo_pkg2_docs_dir}/index.rst", }, ], commit_type, diff --git a/tests/fixtures/monorepos/trunk_based_dev/monorepo_w_tags.py b/tests/fixtures/monorepos/trunk_based_dev/monorepo_w_tags.py index 9687e088f..71bc6e9c7 100644 --- a/tests/fixtures/monorepos/trunk_based_dev/monorepo_w_tags.py +++ b/tests/fixtures/monorepos/trunk_based_dev/monorepo_w_tags.py @@ -88,8 +88,10 @@ def get_repo_definition_4_trunk_only_monorepo_w_tags( monorepo_pkg2_changelog_rst_file: Path, monorepo_pkg1_name: str, monorepo_pkg2_name: str, - monorepo_pkg1_dir: Path, - monorepo_pkg2_dir: Path, + monorepo_pkg1_dir: str, + monorepo_pkg2_dir: str, + monorepo_pkg1_docs_dir: str, + monorepo_pkg2_docs_dir: str, monorepo_pkg1_version_py_file: Path, monorepo_pkg2_version_py_file: Path, stable_now_date: GetStableDateNowFn, @@ -104,6 +106,7 @@ def get_repo_definition_4_trunk_only_monorepo_w_tags( ``` * chore(release): pkg1@0.1.0 [skip ci] (tag: pkg1-v0.1.0, branch: main) + * docs: pkg1 docs modified outside of pkg 1, identified by path filter * feat(pkg1): file modified outside of pkg 1, identified by scope * chore(release): pkg2@0.1.1 [skip ci] (tag: pkg2-v0.1.1) * fix(pkg2-cli): file modified outside of pkg 2, identified by scope @@ -182,21 +185,23 @@ def _get_repo_from_definition( if commit_type != "conventional": raise ValueError(f"Unsupported commit type: {commit_type}") + pkg1_path_filters = (".", f"../../{monorepo_pkg1_docs_dir}") pkg1_commit_parser = ConventionalCommitMonorepoParser( options=ConventionalCommitMonorepoParserOptions( parse_squash_commits=True, ignore_merge_commits=ignore_merge_commits, scope_prefix=f"{monorepo_pkg1_name}-?", - path_filters=(".",), + path_filters=pkg1_path_filters, ) ) + pkg2_path_filters = (".", f"../../{monorepo_pkg2_docs_dir}") pkg2_commit_parser = ConventionalCommitMonorepoParser( options=ConventionalCommitMonorepoParserOptions( parse_squash_commits=pkg1_commit_parser.options.parse_squash_commits, ignore_merge_commits=pkg1_commit_parser.options.ignore_merge_commits, scope_prefix=f"{monorepo_pkg2_name}-?", - path_filters=(".",), + path_filters=pkg2_path_filters, ) ) @@ -247,7 +252,7 @@ def _get_repo_from_definition( ) ), "tool.semantic_release.commit_parser_options.scope_prefix": pkg1_commit_parser.options.scope_prefix, - "tool.semantic_release.commit_parser_options.path_filters": pkg1_commit_parser.options.path_filters, + "tool.semantic_release.commit_parser_options.path_filters": pkg1_path_filters, **(extra_configs or {}), }, }, @@ -271,7 +276,7 @@ def _get_repo_from_definition( ) ), "tool.semantic_release.commit_parser_options.scope_prefix": pkg2_commit_parser.options.scope_prefix, - "tool.semantic_release.commit_parser_options.path_filters": pkg2_commit_parser.options.path_filters, + "tool.semantic_release.commit_parser_options.path_filters": pkg2_path_filters, **(extra_configs or {}), }, }, @@ -524,6 +529,14 @@ def _get_repo_from_definition( "scipy": "ENH:pkg1: file modified outside of pkg 1, identified by scope", "datetime": next(commit_timestamp_gen), }, + { + "cid": (cid_c11_pkg1_docs := "c11_pkg1_docs"), + "conventional": "docs: pkg1 docs modified outside of pkg 1, identified by path filter", + "emoji": ":book: pkg1 docs modified outside of pkg 1, identified by path filter", + "scipy": "DOC: pkg1 docs modified outside of pkg 1, identified by path filter", + "datetime": next(commit_timestamp_gen), + "file_to_change": f"{monorepo_pkg1_docs_dir}/index.rst", + }, ], commit_type, parser=cast( @@ -550,7 +563,10 @@ def _get_repo_from_definition( "details": { "new_version": pkg1_new_version, "dest_files": pkg1_changelog_file_definitions, - "commit_ids": [cid_c10_pkg1_feat], + "commit_ids": [ + cid_c10_pkg1_feat, + cid_c11_pkg1_docs, + ], }, }, change_to_pkg1_dir, From a51eadd8414a7e9cbfa66837ee5a840a6331dfa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Ch=C3=A9rel?= Date: Mon, 1 Dec 2025 12:35:06 +0100 Subject: [PATCH 17/18] fix(parser-conventional-monorepo): fix parser opts validator for outside dir path matches (#1382) Resolves: #1380 --- .../conventional/options_monorepo.py | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/semantic_release/commit_parser/conventional/options_monorepo.py b/src/semantic_release/commit_parser/conventional/options_monorepo.py index 58bcaf47d..241f7aaba 100644 --- a/src/semantic_release/commit_parser/conventional/options_monorepo.py +++ b/src/semantic_release/commit_parser/conventional/options_monorepo.py @@ -43,37 +43,46 @@ class ConventionalCommitMonorepoParserOptions(ConventionalCommitParserOptions): to match them literally. """ - @classmethod @field_validator("path_filters", mode="before") - def convert_strs_to_paths(cls, value: Any) -> tuple[Path, ...]: - values = value if isinstance(value, Iterable) else [value] - results: list[Path] = [] + @classmethod + def convert_strs_to_paths(cls, value: Any) -> tuple[str, ...]: + if isinstance(value, str): + return (value,) - for val in values: - if isinstance(val, (str, Path)): - results.append(Path(val)) - continue + if isinstance(value, Path): + return (str(value),) - raise TypeError(f"Invalid type: {type(val)}, expected str or Path.") + if isinstance(value, Iterable): + results: list[str] = [] + for val in value: + if isinstance(val, (str, Path)): + results.append(str(Path(val))) + continue - return tuple(results) + msg = f"Invalid type: {type(val)}, expected str or Path." + raise TypeError(msg) + + return tuple(results) + + msg = f"Invalid type: {type(value)}, expected str, Path, or Iterable." + raise TypeError(msg) - @classmethod @field_validator("path_filters", mode="after") - def resolve_path(cls, dir_paths: tuple[Path, ...]) -> tuple[Path, ...]: + @classmethod + def resolve_path(cls, dir_path_strs: tuple[str, ...]) -> tuple[str, ...]: return tuple( ( - Path(f"!{Path(str_path[1:]).expanduser().absolute().resolve()}") + f"!{Path(str_path[1:]).expanduser().absolute().resolve()}" # maintains the negation prefix if it exists - if (str_path := str(path)).startswith("!") + if str_path.startswith("!") # otherwise, resolve the path normally - else path.expanduser().absolute().resolve() + else str(Path(str_path).expanduser().absolute().resolve()) ) - for path in dir_paths + for str_path in dir_path_strs ) - @classmethod @field_validator("scope_prefix", mode="after") + @classmethod def validate_scope_prefix(cls, scope_prefix: str) -> str: if not scope_prefix: return "" From 350c48fcb3ffcdfd2e0a235206bc2ecea6b69df0 Mon Sep 17 00:00:00 2001 From: semantic-release Date: Sun, 14 Dec 2025 22:36:59 +0000 Subject: [PATCH 18/18] chore: release v10.5.3 Automatically generated by python-semantic-release --- CHANGELOG.rst | 28 +++++++++++++++++++ .../automatic-releases/github-actions.rst | 14 +++++----- pyproject.toml | 2 +- src/gh_action/requirements.txt | 2 +- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 98a3fcd5e..ff44a7ec7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,34 @@ CHANGELOG ========= +.. _changelog-v10.5.3: + +v10.5.3 (2025-12-14) +==================== + +🪲 Bug Fixes +------------ + +* **cmd-version**: Resolve unauthenticated git repo issues for upstream verification, closes + `#1373`_ (`PR#1388`_, `e164f68`_) + +* **github-action**: Fix failed signing issue when ssh was missing from action environment, closes + `#1376`_ (`PR#1389`_, `18b7eda`_) + +* **parser-conventional-monorepo**: Fix parser opts validator for outside dir path matches, closes + `#1380`_ (`PR#1382`_, `a51eadd`_) + +.. _#1373: https://github.com/python-semantic-release/python-semantic-release/issues/1373 +.. _#1376: https://github.com/python-semantic-release/python-semantic-release/issues/1376 +.. _#1380: https://github.com/python-semantic-release/python-semantic-release/issues/1380 +.. _18b7eda: https://github.com/python-semantic-release/python-semantic-release/commit/18b7edadd7e7dfe42ec43110acf5e1bd8bcd7eb3 +.. _a51eadd: https://github.com/python-semantic-release/python-semantic-release/commit/a51eadd8414a7e9cbfa66837ee5a840a6331dfa1 +.. _e164f68: https://github.com/python-semantic-release/python-semantic-release/commit/e164f682bfa4ca1e7cbe77aa068202fd8094eec7 +.. _PR#1382: https://github.com/python-semantic-release/python-semantic-release/pull/1382 +.. _PR#1388: https://github.com/python-semantic-release/python-semantic-release/pull/1388 +.. _PR#1389: https://github.com/python-semantic-release/python-semantic-release/pull/1389 + + .. _changelog-v10.5.2: v10.5.2 (2025-11-10) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index d1469ffb3..683cf1bfc 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -893,14 +893,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.5.2 + uses: python-semantic-release/python-semantic-release@v10.5.3 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.5.2 + uses: python-semantic-release/publish-action@v10.5.3 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1005,7 +1005,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.5.2 + uses: python-semantic-release/python-semantic-release@v10.5.3 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -1064,14 +1064,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v10.5.2 + uses: python-semantic-release/python-semantic-release@v10.5.3 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.5.2 + uses: python-semantic-release/python-semantic-release@v10.5.3 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1083,7 +1083,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.5.2 + uses: python-semantic-release/publish-action@v10.5.3 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -1091,7 +1091,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.5.2 + uses: python-semantic-release/publish-action@v10.5.3 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/pyproject.toml b/pyproject.toml index 0af0ef360..19b636cb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "10.5.2" +version = "10.5.3" 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 9e4eb9193..c25d6fc5a 100644 --- a/src/gh_action/requirements.txt +++ b/src/gh_action/requirements.txt @@ -1 +1 @@ -python-semantic-release == 10.5.2 +python-semantic-release == 10.5.3