From bafc671eebced7f4b472c0c30cde3d6219b1a513 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Thu, 26 Mar 2026 14:07:29 -0300 Subject: [PATCH 1/4] fix(release): detect published release via git tag when backmerge plugin fails --- .github/workflows/release.yml | 39 ++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac959c3..c2b5390 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -165,17 +165,50 @@ jobs: GIT_COMMITTER_NAME: ${{ secrets.LERIAN_CI_CD_USER_NAME }} GIT_COMMITTER_EMAIL: ${{ secrets.LERIAN_CI_CD_USER_EMAIL }} + # ----------------- Detect release via git tag ----------------- + - name: Detect if release was published + if: always() && steps.semantic.outcome == 'failure' + id: detect-release + run: | + git fetch --tags origin + LATEST_TAG=$(git tag --sort=-v:refname | head -1) + # Check if the latest tag points to a commit on the current branch + if [ -n "$LATEST_TAG" ] && git merge-base --is-ancestor "$(git rev-list -n1 "$LATEST_TAG")" HEAD 2>/dev/null; then + TAG_COMMIT=$(git rev-list -n1 "$LATEST_TAG") + # If the tag was created in the last 5 minutes, the release was just published + TAG_DATE=$(git log -1 --format=%ct "$TAG_COMMIT") + NOW=$(date +%s) + DIFF=$((NOW - TAG_DATE)) + if [ "$DIFF" -lt 300 ]; then + echo "release_published=true" >> "$GITHUB_OUTPUT" + VERSION="${LATEST_TAG#v}" + echo "release_version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "::notice::Release ${LATEST_TAG} was published despite backmerge failure" + else + echo "release_published=false" >> "$GITHUB_OUTPUT" + fi + else + echo "release_published=false" >> "$GITHUB_OUTPUT" + fi + # ----------------- Backmerge Fallback ----------------- - name: Backmerge PR fallback - if: steps.semantic.outcome == 'failure' && steps.semantic.outputs.new_release_published == 'true' + if: | + always() && steps.semantic.outcome == 'failure' && ( + steps.semantic.outputs.new_release_published == 'true' || + steps.detect-release.outputs.release_published == 'true' + ) uses: LerianStudio/github-actions-shared-workflows/src/config/backmerge-pr@develop with: github-token: ${{ steps.app-token.outputs.token }} source-branch: ${{ github.ref_name }} - version: ${{ steps.semantic.outputs.new_release_version }} + version: ${{ steps.semantic.outputs.new_release_version || steps.detect-release.outputs.release_version }} - name: Fail if release itself failed - if: steps.semantic.outcome == 'failure' && steps.semantic.outputs.new_release_published != 'true' + if: | + always() && steps.semantic.outcome == 'failure' && + steps.semantic.outputs.new_release_published != 'true' && + steps.detect-release.outputs.release_published != 'true' run: | echo "::error::Semantic release failed before publishing a new version" exit 1 From a4ba6ea7ed40b1b9f90818576ac83c898618b483 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Thu, 26 Mar 2026 14:15:47 -0300 Subject: [PATCH 2/4] fix(release): pin external actions by commit SHA --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2b5390..af16eef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -103,14 +103,14 @@ jobs: gpg_fingerprint: ${{ steps.import_gpg.outputs.fingerprint }} steps: - - uses: actions/create-github-app-token@v2 + - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 id: app-token with: app-id: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID }} private-key: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY }} - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} @@ -121,7 +121,7 @@ jobs: git reset --hard origin/${{ github.ref_name }} - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v7 + uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7 id: import_gpg with: gpg_private_key: ${{ secrets.LERIAN_CI_CD_USER_GPG_KEY }} @@ -133,7 +133,7 @@ jobs: git_commit_gpgsign: true - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: '20' @@ -148,7 +148,7 @@ jobs: @semantic-release/exec - name: Semantic Release - uses: cycjimmy/semantic-release-action@v6 + uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6 id: semantic continue-on-error: true with: From 53cd23256fefba2fb33e02f497fac0a7719680db Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Thu, 26 Mar 2026 14:22:10 -0300 Subject: [PATCH 3/4] fix(release): use tag snapshot comparison instead of timestamp heuristic --- .github/workflows/release.yml | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af16eef..11f663b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -147,6 +147,13 @@ jobs: npm install --save-dev \ @semantic-release/exec + - name: Snapshot tags before release + id: pre-tags + run: | + git fetch --tags origin + LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1) + echo "latest_tag=${LATEST_TAG:-none}" >> "$GITHUB_OUTPUT" + - name: Semantic Release uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6 id: semantic @@ -171,22 +178,13 @@ jobs: id: detect-release run: | git fetch --tags origin - LATEST_TAG=$(git tag --sort=-v:refname | head -1) - # Check if the latest tag points to a commit on the current branch - if [ -n "$LATEST_TAG" ] && git merge-base --is-ancestor "$(git rev-list -n1 "$LATEST_TAG")" HEAD 2>/dev/null; then - TAG_COMMIT=$(git rev-list -n1 "$LATEST_TAG") - # If the tag was created in the last 5 minutes, the release was just published - TAG_DATE=$(git log -1 --format=%ct "$TAG_COMMIT") - NOW=$(date +%s) - DIFF=$((NOW - TAG_DATE)) - if [ "$DIFF" -lt 300 ]; then - echo "release_published=true" >> "$GITHUB_OUTPUT" - VERSION="${LATEST_TAG#v}" - echo "release_version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "::notice::Release ${LATEST_TAG} was published despite backmerge failure" - else - echo "release_published=false" >> "$GITHUB_OUTPUT" - fi + NEW_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1) + PRE_TAG="${{ steps.pre-tags.outputs.latest_tag }}" + if [ "$NEW_TAG" != "$PRE_TAG" ] && [ -n "$NEW_TAG" ]; then + echo "release_published=true" >> "$GITHUB_OUTPUT" + VERSION="${NEW_TAG#v}" + echo "release_version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "::notice::Release ${NEW_TAG} was published despite backmerge failure" else echo "release_published=false" >> "$GITHUB_OUTPUT" fi From 2aac8af756aa65450cc93118618d4fd96292ed71 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Thu, 26 Mar 2026 14:27:14 -0300 Subject: [PATCH 4/4] refactor(release): extract tag detection into release-tag-snapshot and release-tag-check composites --- .github/workflows/release.yml | 27 +++------- src/config/release-tag-check/README.md | 61 ++++++++++++++++++++++ src/config/release-tag-check/action.yml | 36 +++++++++++++ src/config/release-tag-snapshot/README.md | 46 ++++++++++++++++ src/config/release-tag-snapshot/action.yml | 18 +++++++ 5 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 src/config/release-tag-check/README.md create mode 100644 src/config/release-tag-check/action.yml create mode 100644 src/config/release-tag-snapshot/README.md create mode 100644 src/config/release-tag-snapshot/action.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 11f663b..659c6f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -147,12 +147,10 @@ jobs: npm install --save-dev \ @semantic-release/exec + # ----------------- Snapshot tags before release ----------------- - name: Snapshot tags before release id: pre-tags - run: | - git fetch --tags origin - LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1) - echo "latest_tag=${LATEST_TAG:-none}" >> "$GITHUB_OUTPUT" + uses: LerianStudio/github-actions-shared-workflows/src/config/release-tag-snapshot@develop - name: Semantic Release uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6 @@ -176,37 +174,28 @@ jobs: - name: Detect if release was published if: always() && steps.semantic.outcome == 'failure' id: detect-release - run: | - git fetch --tags origin - NEW_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1) - PRE_TAG="${{ steps.pre-tags.outputs.latest_tag }}" - if [ "$NEW_TAG" != "$PRE_TAG" ] && [ -n "$NEW_TAG" ]; then - echo "release_published=true" >> "$GITHUB_OUTPUT" - VERSION="${NEW_TAG#v}" - echo "release_version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "::notice::Release ${NEW_TAG} was published despite backmerge failure" - else - echo "release_published=false" >> "$GITHUB_OUTPUT" - fi + uses: LerianStudio/github-actions-shared-workflows/src/config/release-tag-check@develop + with: + previous-tag: ${{ steps.pre-tags.outputs.latest-tag }} # ----------------- Backmerge Fallback ----------------- - name: Backmerge PR fallback if: | always() && steps.semantic.outcome == 'failure' && ( steps.semantic.outputs.new_release_published == 'true' || - steps.detect-release.outputs.release_published == 'true' + steps.detect-release.outputs.release-published == 'true' ) uses: LerianStudio/github-actions-shared-workflows/src/config/backmerge-pr@develop with: github-token: ${{ steps.app-token.outputs.token }} source-branch: ${{ github.ref_name }} - version: ${{ steps.semantic.outputs.new_release_version || steps.detect-release.outputs.release_version }} + version: ${{ steps.semantic.outputs.new_release_version || steps.detect-release.outputs.release-version }} - name: Fail if release itself failed if: | always() && steps.semantic.outcome == 'failure' && steps.semantic.outputs.new_release_published != 'true' && - steps.detect-release.outputs.release_published != 'true' + steps.detect-release.outputs.release-published != 'true' run: | echo "::error::Semantic release failed before publishing a new version" exit 1 diff --git a/src/config/release-tag-check/README.md b/src/config/release-tag-check/README.md new file mode 100644 index 0000000..da84492 --- /dev/null +++ b/src/config/release-tag-check/README.md @@ -0,0 +1,61 @@ + + + + + +
Lerian

release-tag-check

+ +Compares the current latest semver (`v*`) tag against a snapshot captured by [`release-tag-snapshot`](../release-tag-snapshot/) to detect whether a new release was published. This is useful when the release action exits with failure due to post-release steps (e.g., backmerge plugin) but the release itself was successful. + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `previous-tag` | The tag captured by `release-tag-snapshot` before the release step | Yes | | + +## Outputs + +| Output | Description | +|--------|-------------| +| `release-published` | `true` if a new tag was created since the snapshot, `false` otherwise | +| `release-version` | The new release version (without `v` prefix), empty if no new release | + +## Usage as composite step + +```yaml +- name: Snapshot tags before release + id: pre-tags + uses: LerianStudio/github-actions-shared-workflows/src/config/release-tag-snapshot@v1.x.x + +- name: Semantic Release + uses: cycjimmy/semantic-release-action@ # v6 + id: semantic + continue-on-error: true + ... + +- name: Check if release was published + id: detect-release + if: always() && steps.semantic.outcome == 'failure' + uses: LerianStudio/github-actions-shared-workflows/src/config/release-tag-check@v1.x.x + with: + previous-tag: ${{ steps.pre-tags.outputs.latest-tag }} + +- name: Backmerge PR fallback + if: | + always() && steps.semantic.outcome == 'failure' && ( + steps.semantic.outputs.new_release_published == 'true' || + steps.detect-release.outputs.release-published == 'true' + ) + uses: LerianStudio/github-actions-shared-workflows/src/config/backmerge-pr@v1.x.x + with: + github-token: ${{ steps.app-token.outputs.token }} + source-branch: ${{ github.ref_name }} + version: ${{ steps.semantic.outputs.new_release_version || steps.detect-release.outputs.release-version }} +``` + +## Required permissions + +```yaml +permissions: + contents: read +``` diff --git a/src/config/release-tag-check/action.yml b/src/config/release-tag-check/action.yml new file mode 100644 index 0000000..b077a9b --- /dev/null +++ b/src/config/release-tag-check/action.yml @@ -0,0 +1,36 @@ +name: Release Tag Check +description: "Compares the current latest semver tag against a previous snapshot to detect if a new release was published." + +inputs: + previous-tag: + description: The tag captured by release-tag-snapshot before the release step + required: true + +outputs: + release-published: + description: "'true' if a new tag was created since the snapshot, 'false' otherwise" + value: ${{ steps.check.outputs.release_published }} + release-version: + description: The new release version (without v prefix), empty if no new release + value: ${{ steps.check.outputs.release_version }} + +runs: + using: composite + steps: + - name: Check for new release tag + id: check + shell: bash + env: + PREVIOUS_TAG: ${{ inputs.previous-tag }} + run: | + git fetch --tags origin + NEW_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1) + if [ "$NEW_TAG" != "$PREVIOUS_TAG" ] && [ -n "$NEW_TAG" ]; then + echo "release_published=true" >> "$GITHUB_OUTPUT" + VERSION="${NEW_TAG#v}" + echo "release_version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "::notice::Release ${NEW_TAG} was published despite post-release failure" + else + echo "release_published=false" >> "$GITHUB_OUTPUT" + echo "release_version=" >> "$GITHUB_OUTPUT" + fi diff --git a/src/config/release-tag-snapshot/README.md b/src/config/release-tag-snapshot/README.md new file mode 100644 index 0000000..1b76f93 --- /dev/null +++ b/src/config/release-tag-snapshot/README.md @@ -0,0 +1,46 @@ + + + + + +
Lerian

release-tag-snapshot

+ +Captures the latest semver (`v*`) tag before a release step runs. Used together with [`release-tag-check`](../release-tag-check/) to detect whether a new release was published — even when the release action reports failure due to post-release steps (e.g., backmerge). + +## Inputs + +None. + +## Outputs + +| Output | Description | +|--------|-------------| +| `latest-tag` | The latest `v*` tag before the release step, or `none` if no tags exist | + +## Usage as composite step + +```yaml +- name: Snapshot tags before release + id: pre-tags + uses: LerianStudio/github-actions-shared-workflows/src/config/release-tag-snapshot@v1.x.x + +- name: Semantic Release + uses: cycjimmy/semantic-release-action@ # v6 + id: semantic + continue-on-error: true + ... + +- name: Check if release was published + id: detect-release + if: always() && steps.semantic.outcome == 'failure' + uses: LerianStudio/github-actions-shared-workflows/src/config/release-tag-check@v1.x.x + with: + previous-tag: ${{ steps.pre-tags.outputs.latest-tag }} +``` + +## Required permissions + +```yaml +permissions: + contents: read +``` diff --git a/src/config/release-tag-snapshot/action.yml b/src/config/release-tag-snapshot/action.yml new file mode 100644 index 0000000..8124b4f --- /dev/null +++ b/src/config/release-tag-snapshot/action.yml @@ -0,0 +1,18 @@ +name: Release Tag Snapshot +description: "Captures the latest semver tag before a release step runs, enabling post-release comparison." + +outputs: + latest-tag: + description: The latest v* tag before the release step (or 'none' if no tags exist) + value: ${{ steps.snapshot.outputs.latest_tag }} + +runs: + using: composite + steps: + - name: Snapshot latest semver tag + id: snapshot + shell: bash + run: | + git fetch --tags origin + LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1) + echo "latest_tag=${LATEST_TAG:-none}" >> "$GITHUB_OUTPUT"