diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94a6a9f..c1ecfd3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,11 +68,62 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Determine Release Finalization + id: finalize + if: steps.changesets.outputs.hasChangesets == 'false' + env: + BEFORE_SHA: ${{ github.event.before }} + PUBLISHED: ${{ steps.changesets.outputs.published }} + run: | + RELEASE_VERSION=$(node -p "require('./package.json').version") + echo "release_version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" + + CHANGE_RANGE_START="${BEFORE_SHA}" + if [ -z "${CHANGE_RANGE_START}" ] || [ "${CHANGE_RANGE_START}" = "0000000000000000000000000000000000000000" ]; then + CHANGE_RANGE_START="$(git rev-list --max-count=1 HEAD^ 2>/dev/null || true)" + fi + + if [ -n "${CHANGE_RANGE_START}" ]; then + CHANGED_FILES="$(git diff --name-only "${CHANGE_RANGE_START}" "${GITHUB_SHA}")" + else + CHANGED_FILES="$(git show --pretty='' --name-only "${GITHUB_SHA}")" + fi + + ROOT_VERSION_CHANGED=false + PACKAGE_RELEASE_FILES_CHANGED=false + + if printf '%s\n' "${CHANGED_FILES}" | grep -qx 'package.json'; then + ROOT_VERSION_CHANGED=true + fi + + if printf '%s\n' "${CHANGED_FILES}" | grep -Eq '^packages/[^/]+/(CHANGELOG\.md|package\.json)$|^NEXT-CHANGELOG-ENTRY\.md$'; then + PACKAGE_RELEASE_FILES_CHANGED=true + fi + + TAG_EXISTS=false + if git rev-parse -q --verify "refs/tags/${RELEASE_VERSION}" >/dev/null; then + TAG_EXISTS=true + fi + + SHOULD_FINALIZE=false + if [ "${PUBLISHED}" = "true" ]; then + SHOULD_FINALIZE=true + elif [ "${ROOT_VERSION_CHANGED}" = "true" ] && [ "${PACKAGE_RELEASE_FILES_CHANGED}" = "true" ] && [ "${TAG_EXISTS}" = "false" ]; then + SHOULD_FINALIZE=true + fi + + echo "root_version_changed=${ROOT_VERSION_CHANGED}" >> "$GITHUB_OUTPUT" + echo "package_release_files_changed=${PACKAGE_RELEASE_FILES_CHANGED}" >> "$GITHUB_OUTPUT" + echo "tag_exists=${TAG_EXISTS}" >> "$GITHUB_OUTPUT" + echo "should_finalize=${SHOULD_FINALIZE}" >> "$GITHUB_OUTPUT" + - name: Publishing Process id: publish - if: steps.changesets.outputs.published == 'true' + if: steps.finalize.outputs.should_finalize == 'true' + env: + RELEASE_VERSION: ${{ steps.finalize.outputs.release_version }} + TAG_EXISTS: ${{ steps.finalize.outputs.tag_exists }} run: | - RELEASE_VERSION=$(node -p "require('./package.json').version") echo "release_version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" if [ -s NEXT-CHANGELOG-ENTRY.md ]; then @@ -81,19 +132,21 @@ jobs: pnpm exec git-cliff --unreleased --output RELEASE_NOTES.md --strip header --bump fi - pnpm exec git-cliff --unreleased --bump --output CHANGELOG.md - : > NEXT-CHANGELOG-ENTRY.md + if [ "${TAG_EXISTS}" = "false" ]; then + pnpm exec git-cliff --unreleased --bump --output CHANGELOG.md + : > NEXT-CHANGELOG-ENTRY.md - git add CHANGELOG.md NEXT-CHANGELOG-ENTRY.md - git commit -m "chore(release): update changelog for ${RELEASE_VERSION}" + git add CHANGELOG.md NEXT-CHANGELOG-ENTRY.md + git commit -m "chore(release): update changelog for ${RELEASE_VERSION}" - git tag -a "${RELEASE_VERSION}" -m "Release ${RELEASE_VERSION}" - git push origin main --follow-tags + git tag -a "${RELEASE_VERSION}" -m "Release ${RELEASE_VERSION}" + git push origin main --follow-tags + fi echo "release_body_path=RELEASE_NOTES.md" >> "$GITHUB_OUTPUT" - name: Create GitHub Release - if: steps.changesets.outputs.published == 'true' + if: steps.finalize.outputs.should_finalize == 'true' uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.publish.outputs.release_version }} diff --git a/README.md b/README.md index 7e882b4..64b5c99 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ Its key feature is a two-step release process: 2. Publish: Once the PR is merged, the action publishes non-private packages, regenerates the root `CHANGELOG.md`, creates a Git tag, and publishes a polished GitHub Release. + If a package version was already published during validation, the workflow still finalizes the + repository release as long as the version PR landed and the root tag does not exist yet. Key Features: - 🚀 **Turborepo-Optimized**: High-speed CI leveraging Turborepo's caching and task orchestration. @@ -88,10 +90,12 @@ graph TD H -- Has changesets --> I["Create or update
Version Packages PR"]; I --> J[PR Merged by User]; - H -- No changesets, publishable package changed --> K["pnpm changeset publish"]; - K --> L["git-cliff regenerates
root CHANGELOG.md"]; - L --> M["Commit changelog
Create and push tag"]; - M --> N[🚀 Create GitHub Release]; + H -- No changesets --> K["pnpm changeset publish"]; + K --> L{"Published now or
version PR landed
without root tag?"}; + L -- Yes --> M["git-cliff regenerates
root CHANGELOG.md"]; + L -- No --> O["No repository release update"]; + M --> P["Commit changelog
Create and push tag"]; + P --> N[🚀 Create GitHub Release]; end E --> F;