Release Packages #1492
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Packages | |
| run-name: > | |
| ${{ | |
| github.event_name == 'workflow_dispatch' && format('Release {0} ({1}) - {2}', github.event.inputs.version, github.event.inputs.release_type, github.event.inputs.packages) || 'Release Packages' | |
| }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| packages: | |
| description: "Packages to release (comma-separated)" | |
| required: true | |
| type: string | |
| default: "@novu/js,@novu/react,@novu/nextjs,@novu/react-native" | |
| version: | |
| description: "Version to release (e.g., v3.0.0)" | |
| required: true | |
| type: string | |
| default: "v3.0.0" | |
| previous_tag: | |
| description: "Previous tag to generate changelog from (e.g., @novu/js@v3.4.0)" | |
| required: true | |
| type: string | |
| default: "@novu/js@v3.4.0" | |
| release_type: | |
| description: "Type of release" | |
| required: true | |
| type: choice | |
| options: | |
| - stable | |
| - nightly | |
| - rc | |
| default: stable | |
| pull_request: | |
| types: [closed] | |
| branches: [next, main] | |
| env: | |
| NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} | |
| jobs: | |
| release: | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| id-token: write | |
| outputs: | |
| pr_number: ${{ github.event.inputs.release_type == 'stable' && steps.create-pr.outputs.pull-request-number || '' }} | |
| pr_url: ${{ github.event.inputs.release_type == 'stable' && steps.create-pr.outputs.pull-request-url || '' }} | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| ref: ${{ github.ref_name }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| with: | |
| version: 11.0.9 | |
| run_install: false | |
| - name: Setup Node Version | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: "22.22.1" | |
| cache: "pnpm" | |
| - name: Upgrade npm for Trusted Publishing | |
| run: | | |
| npm install -g npm@latest | |
| echo "npm version: $(npm --version) (>= 11.5.1 required for trusted publishing)" | |
| - name: Install Dependencies | |
| shell: bash | |
| run: | | |
| pnpm ci | |
| pnpm nx --version | |
| pnpm list nx | |
| - name: Set version for nightly or rc | |
| if: github.event.inputs.release_type != 'stable' | |
| run: | | |
| COMMIT_SHA=$(git rev-parse --short HEAD) | |
| if [ "${{ github.event.inputs.release_type }}" = "nightly" ]; then | |
| DATE=$(date +'%Y%m%d') | |
| echo "RELEASE_VERSION=${{ github.event.inputs.version }}-nightly.${DATE}.${COMMIT_SHA}" >> $GITHUB_ENV | |
| echo "Using nightly version: $RELEASE_VERSION" | |
| elif [ "${{ github.event.inputs.release_type }}" = "rc" ]; then | |
| echo "RELEASE_VERSION=${{ github.event.inputs.version }}-rc.${COMMIT_SHA}" >> $GITHUB_ENV | |
| echo "Using rc version: $RELEASE_VERSION" | |
| fi | |
| - name: Configure Git | |
| run: | | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --global user.name "github-actions[bot]" | |
| - name: Release version (without commit) | |
| run: | | |
| if [ "${{ github.event.inputs.release_type }}" = "nightly" ]; then | |
| echo "Running nightly release with version: ${{ env.RELEASE_VERSION }}" | |
| pnpm nx release version ${{ env.RELEASE_VERSION }} --projects=${{ github.event.inputs.packages }} --preid nightly --git-commit=false --verbose | |
| elif [ "${{ github.event.inputs.release_type }}" = "rc" ]; then | |
| echo "Running rc release with version: ${{ env.RELEASE_VERSION }}" | |
| pnpm nx release version ${{ env.RELEASE_VERSION }} --projects=${{ github.event.inputs.packages }} --preid rc --git-commit=false --verbose | |
| else | |
| echo "Running stable release with version: ${{ github.event.inputs.version }}" | |
| pnpm nx release version ${{ github.event.inputs.version }} --projects=${{ github.event.inputs.packages }} --git-commit=false --verbose | |
| fi | |
| - name: Generate changelog (without commit) | |
| run: | | |
| if [ "${{ github.event.inputs.release_type }}" = "stable" ]; then | |
| pnpm nx release changelog ${{ github.event.inputs.version }} --projects=${{ github.event.inputs.packages }} --from=${{ github.event.inputs.previous_tag }} --git-commit=false | |
| else | |
| pnpm nx release changelog ${{ env.RELEASE_VERSION }} --projects=${{ github.event.inputs.packages }} --from=${{ github.event.inputs.previous_tag }} --git-commit=false | |
| fi | |
| - name: Build packages | |
| env: | |
| NX_NO_CLOUD: ${{ secrets.NX_CLOUD_ACCESS_TOKEN == '' && 'true' || 'false' }} | |
| run: | | |
| if [ "${{ github.event.inputs.release_type }}" != "stable" ]; then | |
| pnpm run build:packages | |
| else | |
| echo "Skipping build for stable release (will be built after PR merge)" | |
| fi | |
| - name: Publish packages (nightly and rc only) | |
| if: github.event.inputs.release_type != 'stable' | |
| run: | | |
| if [ "${{ github.event.inputs.release_type }}" = "nightly" ]; then | |
| echo "📦 Publishing nightly release with OIDC trusted publishing..." | |
| pnpm nx run-many -t nx-release-publish --projects=${{ github.event.inputs.packages }} -- --tag=nightly --provenance | |
| elif [ "${{ github.event.inputs.release_type }}" = "rc" ]; then | |
| echo "📦 Publishing RC release with OIDC trusted publishing..." | |
| pnpm nx run-many -t nx-release-publish --projects=${{ github.event.inputs.packages }} -- --tag=next --provenance | |
| fi | |
| - name: Create Pull Request | |
| if: github.event.inputs.release_type == 'stable' | |
| id: create-pr | |
| uses: peter-evans/create-pull-request@4e1beaa7521e8b457b572c090b25bd3db56bf1c5 # v5 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| commit-message: "chore: release ${{ github.event.inputs.version }} (${{ github.event.inputs.packages }})" | |
| title: "🚀 Release ${{ github.event.inputs.version }} - ${{ github.event.inputs.packages }}" | |
| body: | | |
| ## 🚀 Release ${{ github.event.inputs.version }} | |
| This PR contains the release changes for version **${{ github.event.inputs.version }}** | |
| ### 📦 Packages Released: | |
| ``` | |
| ${{ github.event.inputs.packages }} | |
| ``` | |
| ### 🔄 Changes: | |
| - Updated package versions to ${{ github.event.inputs.version }} | |
| - Generated changelogs from ${{ github.event.inputs.previous_tag }} | |
| **After merging this PR:** | |
| - Packages will be built and published to npm with provenance | |
| - Git tags will be created | |
| - GitHub releases will be created | |
| **Please review the changes and merge when ready.** | |
| branch: release-${{ github.event.inputs.version }} | |
| base: ${{ github.ref_name }} | |
| delete-branch: false | |
| labels: automated-npm-release | |
| publish-stable: | |
| if: github.event_name == 'pull_request' && github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release-') && contains(github.event.pull_request.labels.*.name, 'automated-npm-release') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| ref: ${{ github.event.pull_request.base.ref }} | |
| - name: Extract version and packages from PR | |
| id: extract-info | |
| run: | | |
| PR_TITLE='${{ github.event.pull_request.title }}' | |
| echo "=== PR Title ===" | |
| echo "$PR_TITLE" | |
| echo "" | |
| echo "=== Extraction ===" | |
| VERSION=$(echo "$PR_TITLE" | sed -n 's/.*Release \([v0-9\.]*\).*/\1/p') | |
| PACKAGES=$(echo "$PR_TITLE" | sed -n 's/.*- \(@novu.*\)/\1/p') | |
| if [ -z "$VERSION" ]; then | |
| echo "❌ ERROR: Failed to extract version from PR title" | |
| echo "Expected format: '🚀 Release vX.Y.Z - @novu/package1,@novu/package2'" | |
| exit 1 | |
| fi | |
| if [ -z "$PACKAGES" ]; then | |
| echo "❌ ERROR: Failed to extract packages from PR title" | |
| echo "Expected format: '🚀 Release vX.Y.Z - @novu/package1,@novu/package2'" | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "packages=$PACKAGES" >> $GITHUB_OUTPUT | |
| echo "✅ Extracted version: $VERSION" | |
| echo "✅ Extracted packages: $PACKAGES" | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| with: | |
| version: 11.0.9 | |
| run_install: false | |
| - name: Setup Node Version | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: "22.22.1" | |
| cache: "pnpm" | |
| - name: Upgrade npm for Trusted Publishing | |
| run: | | |
| npm install -g npm@latest | |
| echo "npm version: $(npm --version) (>= 11.5.1 required for trusted publishing)" | |
| - name: Install Dependencies | |
| shell: bash | |
| run: | | |
| pnpm ci | |
| - name: Build packages | |
| env: | |
| NX_NO_CLOUD: ${{ secrets.NX_CLOUD_ACCESS_TOKEN == '' && 'true' || 'false' }} | |
| run: pnpm run build:packages | |
| - name: Publish packages to NPM | |
| run: | | |
| echo "📦 Publishing stable release with OIDC trusted publishing..." | |
| pnpm nx run-many -t nx-release-publish --projects=${{ steps.extract-info.outputs.packages }} -- --tag=latest --provenance | |
| - name: Create and push tags | |
| run: | | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --global user.name "github-actions[bot]" | |
| echo "Creating tags for packages: ${{ steps.extract-info.outputs.packages }}" | |
| IFS=',' read -ra PACKAGES <<< "${{ steps.extract-info.outputs.packages }}" | |
| for package in "${PACKAGES[@]}"; do | |
| package=$(echo "$package" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
| tag_name="${package}@${{ steps.extract-info.outputs.version }}" | |
| echo "Creating tag: $tag_name" | |
| git tag "$tag_name" | |
| echo "Pushing tag: $tag_name" | |
| git push origin "$tag_name" | |
| done | |
| - name: Create GitHub Releases | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "Creating GitHub releases for packages: ${{ steps.extract-info.outputs.packages }}" | |
| IFS=',' read -ra PACKAGES <<< "${{ steps.extract-info.outputs.packages }}" | |
| for package in "${PACKAGES[@]}"; do | |
| package=$(echo "$package" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
| tag_name="${package}@${{ steps.extract-info.outputs.version }}" | |
| echo "Creating GitHub release for $package with tag: $tag_name" | |
| package_name=$(echo $package | sed 's/@novu\///') | |
| release_body="Release of ${package} version ${{ steps.extract-info.outputs.version }}" | |
| possible_paths=( | |
| "packages/${package_name}/CHANGELOG.md" | |
| "${package_name}/CHANGELOG.md" | |
| "CHANGELOG.md" | |
| ) | |
| for changelog_path in "${possible_paths[@]}"; do | |
| if [ -f "$changelog_path" ]; then | |
| echo "Found changelog at: $changelog_path" | |
| changelog_content=$(awk -v version="${{ steps.extract-info.outputs.version }}" ' | |
| BEGIN { capture=0 } | |
| /^## / || /^# / || /^v[0-9]+\./ { | |
| if (capture) exit | |
| if ($0 ~ "(^|[^0-9.])" version "([^0-9.]|$)") { | |
| capture=1 | |
| next | |
| } | |
| } | |
| capture { print } | |
| ' "$changelog_path" | sed '/^$/d' | head -50) | |
| if [ -n "$changelog_content" ]; then | |
| release_body="$changelog_content" | |
| break | |
| fi | |
| fi | |
| done | |
| gh release create "$tag_name" \ | |
| --title "$tag_name" \ | |
| --notes "$release_body" \ | |
| --target ${{ github.event.pull_request.base.ref }} | |
| echo "✅ Created GitHub release for $tag_name" | |
| done |